【2023.07】GP-人工智能深度学习系统班-完结-课件齐全(189.14G)

01 直播课回放

02 深度学习必备核心算法

01 神经网络算法解读

03 深度学习核心框架 PyTorch

01 PyTorch 框架介绍与配置安装

01 PyTorch 框架与其他框架区别分析

  • 2015 caffe 写配置文件就能搭网络,安装麻烦
  • 2016 tensorflow 大厂背书,api 难
  • 2017 keras tensorflow 封装,api 变简单了,高校做实验流行
  • 2018 tensorflow 2.x google 收购 keras
  • 2019 pytorch api 简单,看实战案例就能上手,套模板,向后支持好

02 CPU 与 GPU 版本安装方法解读

cpu版本只需要有anaconda,然后pip安装就行

gpu 版本要安 cuda

推荐转 cuda 11.x 版本


02 使用神经网络进行分类任务

01 数据集与任务概述


tensorflow 和 pytorch 都是用 tensor(张量、矩阵),numpy 的数据结构是 ndarray

神经网络就是对矩阵进行各种运算

import torch

print(torch.__version__)  # 1.12.0+cpu
# %matplotlib inline


# 下载数据
from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / 'mnist'

PATH.mkdir(parents=True, exist_ok=True)

URL = "http://deeplearning.net/data/mnist/"
FILENAME = 'mnist.pkl.gz'

if not (PATH / FILENAME).exists():
    content = requests.get(URL + FILENAME).content
    (PATH / FILENAME).open("wb").write(content)

# 加载数据
import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), 'rb') as f:
    ((X_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding='latin-1')

from matplotlib import pyplot as plt

plt.switch_backend('TkAgg')
import numpy as np

# imshow:展示图片,28*28=784
plt.imshow(X_train[0].reshape((28, 28)), cmap="gray")
print(X_train[0].shape)  # (784,) -> 其中一个样本
plt.show()
# 50000个样本
print('xtrain shape:\n', X_train.shape)  # (50000, 784)
# 样本是哪个数字
print('ytrain:\n', y_train[:5])

# 数据要转成tensor才能训练

02 基本模块应用测试

全连接层,就是每层都是 wx+b 的操作

最后一层输出 10 个数据,表示 10 分类,输出结果是输入在各个分类的概率

# 数据要转成tensor才能训练
import torch

# 映射成tensor格式
# 参数1: 要对参数2执行的函数
# 参数2: 要被操作的数据
X_train, y_train, x_valid, y_valid = map(
    torch.tensor, (X_train, y_train, x_valid, y_valid)
)

# n, c = X_train.shape
# X_train, X_train.shape, y_train.min(), y_train.max()
print('x train:\n', X_train[:5])
print('x train shape:\n', X_train.shape)  # torch.Size([50000, 784])

# torch.nn.functional 很多层和函数在这里会见到
# 实际构建可能会用别的方式
import torch.nn.functional as F

# 交叉熵
loss_func = F.cross_entropy


def model(xb):
    # mm就是矩阵乘法
    # x*w+b
    return xb.mm(weights) + bias


# 一次训练的样本个数 batch_size
bs = 64
xb = X_train[0:bs]
yb = y_train[0:bs]
# 784*10的服从正态分布的随机数矩阵
weights = torch.randn([784, 10], dtype=torch.float, requires_grad=True)
bias = torch.zeros(10, requires_grad=True)

# 计算交叉熵
print('loss:\n', loss_func(model(xb), yb))

03 网络结构定义方法




# 用torch创建模型,简化代码
from torch import nn


class Minst_NN(nn.Module):
    def __init__(self):
        super().__init__()
        # 前一层输出是后一层输入,所以784*128后是128,x
        # Linear -> wx+b
        # 输入784个像素点,输出128个特征
        self.hidden1 = nn.Linear(784, 128)
        self.hidden2 = nn.Linear(128, 256)
        # self.hidden3 = nn.Linear(256, 512)
        # 输出层
        self.out = nn.Linear(256, 10)
        # 训练时随机去掉多少神经元(每次只有一部分神经元参与训练),防止过拟合
        self.dropout = nn.Dropout(0.5)

    # 前向传播,需要自己定义
    # 反向传播,torch自动定义
    def forward(self, x):
        # x是输入 784*batch_size
        # F.relu是激活函数
        x = F.relu(self.hidden1(x))
        # 随机去掉神经元(每个全连接层都要做)
        x = self.dropout(x)
        x = F.relu(self.hidden2(x))
        x = self.dropout(x)
        x = self.out(x)
        return x


net = Minst_NN()
print('model:\n', net)

# torch自动帮我们初始化参数(权重w,偏置值bias)
for name, parameter in net.named_parameters():
    # name指某层的某个参数的名称
    print('name:\n', name)
    # parameter是某层的某个参数
    print('parameter:\n', parameter)
    print('parameter size:\n', parameter.size())

04 数据源定义简介


# 使用TensorDataset和DataLoader简化
from torch.utils.data import TensorDataset  # 用的少
from torch.utils.data import DataLoader

train_ds = TensorDataset(X_train, y_train)
# dataloader把数据按batch_size打包,然后发送给gpu
# shuffle是打乱数据
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)


def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        # DataLoader(valid_ds, batch_size=bs)
        DataLoader(valid_ds, batch_size=bs * 2)
    )


import numpy as np


def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    '''
    训练
    :param steps: 迭代多少次
    :param model: 模型
    :param loss_func: F.cross 交叉熵计算函数
    :param opt: 优化器
    :param train_dl:
    :param valid_dl:
    :return:
    '''
    for step in range(steps):
        # 切换为训练模式
        # 会更新w和b
        model.train()
        for xb, yb in train_dl:
            # xb [batch_size,784]
            # yb [batch_size]
            # 计算损失并优化
            loss_batch(model, loss_func, xb, yb, opt)

        # 切换为验证模式
        model.eval()
        # 没有梯度,不更新w和b
        with torch.no_grad():
            # nums是输入的个数,losses是损失
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        # 计算总损失(losses*nums).sum() -> 平均 / nums.sum()
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)
        print(f'当前step:{step},验证集损失:{val_loss}')

05 损失与训练模块分析

一个 epoch 是将整个训练集训练一遍,每个 batch 是每次训练用的单个数据包大小

from torch import optim  # 优化器


def get_model():
    model = Minst_NN()
    # 参数1:更新哪些参数
    # 参数2:学习率
    return model, optim.SGD(model.parameters(), lr=0.001)
    # return model, optim.Adam(model.parameters(), lr=0.001)


def loss_batch(model, loss_func, xb, yb, opt=None):
    # 计算预测值和真实值的loss(误差)
    loss = loss_func(model(xb), yb)

    # 如果有优化器
    if opt is not None:
        # 反向传播计算梯度,
        loss.backward()
        # 梯度计算好后,可以进行单次优化,参数更新
        opt.step()
        # 默认pytorch会累加上之前的梯度
        # 这里清空梯度,防止出现累加
        opt.zero_grad()

    # loss.item -> 取出张量具体位置的元素元素值
    # 返回loss
    return loss.item(), len(xb)

06 训练一个基本的分类模型

# 训练
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(100, model, loss_func, opt, train_dl, valid_dl)

correct = 0
total = 0
for xb, yb in valid_dl:
    outputs = model(xb)
    # max(数据,比较的维度)
    # outputs的列是不同结果的可能性
    # max_val,index = max(outputs.data, 1) -> 求outputs里值最大的列
    # 这里的最大值是结果概率,索引表示是哪一列,索引才是我们需要的
    _, predicted = torch.max(outputs.data, 1)  # 最大的值和索引
    # 总样本数
    total += yb.size(0)  # + 64
    # .item -> 得到tensor里的值
    # correct -> 预测正确的个数
    correct += (predicted == yb).sum().item()

print('correct:\n', correct)
print('total:\n', total)
# correct / total -> 小数
# 100 * correct / total -> 十位数 %
print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

07 参数对结果的影响

Adam用的比较多

03 神经网络回归任务-气温预测

标准化

batch 分批计算,减少内存压力

Adam 和 SGD 思想是一样的,但是 Adam 在更新参数时记录了‘惯性’,后续优化会考虑这个惯性,从而更快地到达最优解

如何添加更多层

# 回归:输入很多样本,有一些特征以及结果,经过神经网络计算后,得出一个预测值
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')
import torch
import torch.optim as opt
import warnings

# 去掉一些没用的警告
warnings.filterwarnings('ignore')
# %matplotlib inline

features = pd.read_csv('temps.csv')
print('features:\n', features.head())
print('数据维度:\n', features.shape)  # (348, 9)

# 处理时间数据
import datetime

# 年月日
years = features['year']
months = features['month']
days = features['day']

# 格式化,datetime格式,为了画图方便
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]

print('dates:\n', dates[:5])

# 画图
# 指定默认风格
plt.style.use('fivethirtyeight')

# 设置布局
# nrows 2 ncols 2 -> 2行2列
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(10, 10))
# 改变x轴坐标的显示方法可以斜着表示,不用平着挤一堆
# rotation=45: 倾斜45度
fig.autofmt_xdate(rotation=45)

# 标签值
# plot(x,y)
ax1.plot(dates, features['actual'])
ax1.set_xlabel('')
ax1.set_ylabel('Temperature')
ax1.set_title('Max Temp')

# 昨天
ax2.plot(dates, features['temp_1'])
ax2.set_xlabel('')
ax2.set_ylabel('Temperature')
ax2.set_title('Previous Max Temp')

# 前天
ax3.plot(dates, features['temp_2'])
ax3.set_xlabel('Date')
ax3.set_ylabel('Temperature')
ax3.set_title('Two Days Prior Max Temp')

# friend
ax4.plot(dates, features['friend'])
ax4.set_xlabel('Date')
ax4.set_ylabel('Temperature')
ax4.set_title('Friend Estimate')

# 不同图片之间的间隔
plt.tight_layout(pad=2)
plt.show()

# feature['week']的值是Fri之类的单词缩写,可以使用one-hot给这些值编码出唯一的数值编号
# 独热编码 -> get_dummies -> 自动判断非数值,
# 比如week会被分成7列(因为只有7种值),
# 然后是哪一个(如Fri),则那列的值为1(week_fri: True)
features = pd.get_dummies(features)
print('feature:\n', features[:5])

# 标签
labels = np.array(features['actual'])
# 在特征中去掉标签列
features = features.drop('actual', axis=1)
# 保存列名
feature_list = list(features.columns)
# 转换成合适的格式
features = np.array(features)

print('features shape:\n', features.shape)  # (348, 14)

# 预处理 -> 标准化(去量纲),因为模型会将数值大的特征(col)看作重要的,但是实际上并不能这么看
from sklearn import preprocessing

# 去均值,比上标准差
# StandardScaler: 标准化
# fit_transform: 执行并转换
input_features = preprocessing.StandardScaler().fit_transform(features)
print('input_features:\n', input_features[:5])
print('input_features shape:\n', input_features.shape)  # (348, 14)

# 构建网络模型
x = torch.tensor(input_features, dtype=float)
y = torch.tensor(labels, dtype=float)

# 权重参数初始化
# input_features shape (348,14) -> 14,128 -> 128 -> 128,1 -> 1
weights = torch.randn((14, 128), dtype=float, requires_grad=True)
biases = torch.randn(128, dtype=float, requires_grad=True)
weights2 = torch.randn((128, 1), dtype=float, requires_grad=True)
biases2 = torch.randn(1, dtype=float, requires_grad=True)

learning_rate = 0.001
losses = []

for i in range(1000):
    # 计算隐层
    hidden = x.mm(weights) + biases
    # 加入激活函数
    hidden = torch.relu(hidden)
    # 预测结果
    predictions = hidden.mm(weights2) + biases2
    # 计算损失(均方误差)
    loss = torch.mean((predictions - y) ** 2)
    # 记录损失
    losses.append(loss.data.numpy())  # 画图需要numpy

    # 打印损失值
    if i % 100 == 0:
        print('loss: ', loss)
    # 反向传播计算
    loss.backward()

    # 更新参数
    # 梯度(导数)的方向实际上是向上的(正数),所以要加上负号,改为下降
    weights.data.add_(-learning_rate * weights.grad.data)
    biases.data.add_(-learning_rate * biases.grad.data)
    weights2.data.add_(-learning_rate * weights2.grad.data)
    biases2.data.add_(-learning_rate * biases2.grad.data)

    # 每次迭代都清空
    weights.grad.data.zero_()
    biases.grad.data.zero_()
    weights2.grad.data.zero_()
    biases2.grad.data.zero_()

# torch.Size([348, 1])
print(predictions.shape)

# 使用torch构建网络
# 输入的特征个数 14
input_size = input_features.shape[1]
hidden_size = 128
output_size = 1
# 分批计算,减少内存压力
batch_size = 16
# 按顺序构建网络
# torch会自动初始化,只需要指定size就行
my_nn = torch.nn.Sequential(
    # 线性层(全连接层)
    torch.nn.Linear(input_size, hidden_size),
    # 激活函数
    torch.nn.Sigmoid(),  # nn.Relu()
    torch.nn.Linear(hidden_size, output_size),
)
# 均方误差
cost = torch.nn.MSELoss(reduction='mean')
optimizer = torch.optim.Adam(my_nn.parameters(), lr=0.001)

print(len(input_features))

# 训练网络
losses = []
for i in range(1000):
    batch_loss = []
    # MINI-Batch方法来训练
    # len(input_features) = 348
    # 从0~348,每次取64(0->64->128->...)
    for start in range(0, len(input_features), batch_size):
        # 计算end(取得的batch的右界),如果超了,就设为348( == len(input_features))
        end = start + batch_size if start + batch_size < len(input_features) else len(input_features)
        # 得到 1 batch_size的x和y数据
        xx = torch.tensor(input_features[start:end], dtype=torch.float, requires_grad=True)
        yy = torch.tensor(labels[start:end], dtype=torch.float, requires_grad=True)
        # 预测
        prediction = my_nn(xx)
        # 计算损失
        loss = cost(prediction, yy)
        # 清0梯度
        optimizer.zero_grad()
        # 反向传播计算梯度
        loss.backward(retain_graph=True)
        # 根据梯度进行优化
        optimizer.step()
        batch_loss.append(loss.data.numpy())

    # 打印损失
    if i % 100 == 0:
        losses.append(np.mean(batch_loss))
        print('mean batch_loss:\n', i, np.mean(batch_loss))

x = torch.tensor(input_features, dtype=torch.float)
predict = my_nn(x).data.numpy()

# 转换日期格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day))
         for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]

# 创建一个表格来存日期和其对应的标签数值
true_data = pd.DataFrame(data={'date': dates, 'actual': labels})

# 同理,再创建一个来存日期和其对应的模型预测值
months = features[:, feature_list.index('month')]
days = features[:, feature_list.index('day')]
years = features[:, feature_list.index('year')]

test_dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day))
              for year, month, day in zip(years, months, days)]

test_dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in test_dates]

predictions_data = pd.DataFrame(data={'date': test_dates, 'prediction': predict.reshape(-1)})

# 画图
# 真实值
plt.plot(true_data['date'], true_data['actual'], 'b-', label='actual')

# 预测值
plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label='prediction')
# 倾斜x轴
plt.xticks(rotation=60)
plt.legend()

# 图名
plt.xlabel('Date')
plt.ylabel('Maximum Temperature (F)')
plt.title('Actual and Predicted Values')
plt.show()

04 卷积网络参数解读分析

01 输入特征通道分析

卷积网络里的数据是有关联的(表示图片的不同点),之前那个线性回归的输入,每个数据之间是没关系的



# 卷积网络中的输入和层与传统神经网络有区别,需要重新设计,训练模块基本一致
import torch
import torch.nn as nn
import torch.optim as opt
import torch.nn.functional as F
# datasets包里有一些数据集和对数据集的操作
# transforms包里主要是预处理
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np

# %matplotlib inline

# 读取数据
# 图像数据集,用CNN
# 图像有三通道(RGB),还有灰度图
# 这里是灰度图,也就是,batch_size*28*28*1 (1是灰度)
# 定义超参数
input_size = 28  # 图像尺寸28 -> 28*28=748
num_classes = 10  # 标签的种类数
num_epochs = 3  # 训练的总循环周期
batch_size = 64  # 一批次的大小,64张图

# 训练集
train_dataset = datasets.MNIST(root='./data',
                               train=True,
                               transform=transforms.ToTensor(),
                               download=True)

# 测试集
test_dataset = datasets.MNIST(root='./data',
                              train=False,
                              transform=transforms.ToTensor())

# 构建batch数据
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=True)

02 卷积网络参数解读

padding 就是给输入加一圈 0,因为边界有时候被卷积核计算的次数比内部的少,所以加一圈可以增加被卷积计算的次数

右上角的公式可以计算 padding 取什么值,如果有小数,就向下取整(pytorch)

03 卷积网络模型训练

# 构建卷积网络
# > 一般卷积层,relu层,池化层可以写一套
# > 卷积最后结果还是一个特征图,需要把图转换成向量才能做分类或者回归任务
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(  # 输入大小 (1,28,28)
            # 向量做1维卷积 -> 基本用不上 -> 用全连接比较多
            # 图像做2维卷积
            # 视频(时间+多图)做3维卷积
            nn.Conv2d(
                in_channels=1,  # 灰度图,1个管道
                out_channels=16,  # 要得到多少个特征图 -> 卷积核个数
                kernel_size=5,  # 卷积核大小(5x5) -> 越小,可以得到的特征越多
                stride=1,  # 步长
                padding=2,  # 如果希望卷积后大小跟原来一样,需要设置padding=(kernel_size-1)/2 if stride=1
            ),
            nn.ReLU(),  # relu层,激活
            # 一般pool池化就是把输出的shape变为原来的一半
            # 在卷积神经网络中通常会在相邻的卷积层之间加入一个池化层,
            # 池化层可以有效的缩小参数矩阵的尺寸,
            # 从而减少最后连接层的中的参数数量。
            # 所以加入池化层可以加快计算速度和防止过拟合的作用。
            nn.MaxPool2d(kernel_size=2),  # 进行池化操作(2x2区域),输出结果为(16,14,14) 16=out_channels
        )
        self.conv2 = nn.Sequential(  # 下一个套餐的输入(16,14,14)
            # 参数和上面一样
            nn.Conv2d(16, 32, 5, 1, 2),  # 输出(32,14,14)
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),  # 输出(32,7,7)
        )
        self.conv3 = nn.Sequential(  # 下一个套餐的输入(32,7,7)
            nn.Conv2d(32, 64, 5, 1, 2),  # 输出(64,7,7)
            nn.ReLU(),
        )
        # 输入64*7*7的x,得到在10个可能分类的结果
        self.out = nn.Linear(64 * 7 * 7, 10)  # 全连接层

    def forward(self, x):
        # 输入的是 1 batch_size -> 64 * 1*28*28
        # print(x.size()) # torch.Size([64, 1, 28, 28])
        x = self.conv1(x)
        x = self.conv2(x)
        # print(x.size()) # torch.Size([64, 32, 7, 7])
        x = self.conv3(x)
        # print(x.size())  # torch.Size([64, 64, 7, 7])
        # reshape -> 64*7*7 -> 64*N (-1自动推导,其值使各维度相乘总数不变)
        x = x.view(x.size(0), -1)  # flatten操作,结果为(batch_size,64*7*7)
        # print(x.size()) # torch.Size([64, 3136])
        output = self.out(x)
        return output


# 评估标准: 准确率
def accuracy(predictions, labels):
    # max_val,max_idx = max(数据,比较的维度)
    pred = torch.max(predictions.data, 1)[1]
    # view_as: 该函数的作用是将调用函数的变量,转变为同参数tensor同样的形状。
    # pred.eq(labels.data).sum() -> 找出预测正确的个数
    rights = pred.eq(labels.data.view_as(pred)).sum()
    return rights, len(labels)


# 训练模型
# 实例化
net = CNN()
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = opt.Adam(net.parameters(), lr=0.001)

# 训练循环
for epoch in range(num_epochs):
    # 当前epoch的结果保存下来
    train_rights = []

    # enumerate:返回值有两个:一个是序号,一个是数据(包含训练数据和标签)
    for batch_idx, (data, target) in enumerate(train_loader):  # 对容器中的每一批进行循环
        # 训练模式,可更新梯度
        net.train()
        output = net(data)
        loss = criterion(output, target)
        # 梯度清0
        optimizer.zero_grad()
        # 反向传播计算梯度
        loss.backward()
        # 优化
        optimizer.step()
        # 计算准确率 -> return rights, len(labels)
        right = accuracy(output, target)
        train_rights.append(right)

        if batch_idx % 100 == 0:
            # 验证模式,不更新梯度
            net.eval()
            val_rights = []

            for (data, target) in test_loader:
                output = net(data)
                right = accuracy(output, target)
                val_rights.append(right)

            # 准确率计算
            # sum(right%) , sum(len(val))
            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))

            print('当前epoch: {} [{}/{} ({:.0f}%)]\t损失: {:.6f}\t训练集准确率: {:.2f}%\t测试集准确率: {:.2f}%'.format(
                epoch, batch_idx * batch_size, len(train_loader.dataset),
                       100 * batch_idx / len(train_loader),
                loss.data,
                       100 * train_r[0].numpy() / train_r[1],
                       100 * val_r[0].numpy() / val_r[1],
            ))

后面很多使用经典模型,很多参数和模型结构都不需要调整

05 图像识别模型与训练策略(重点)

01 任务分析与图像数据基本处理



import os
import matplotlib.pyplot as plt
# %matplotlib inline
import numpy as np
import torch
from torch import nn
import torch.optim as optim
import torchvision
# https://pytorch.org/docs/stable/torchvision/index.html
from torchvision import transforms, models, datasets
import imageio
import time
import warnings

warnings.filterwarnings('ignore')
import random
import sys
import copy
import json
from PIL import Image

# 数据读取与预处理
data_dir = './flower_data/'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'

02 数据增强模块

可以通过变换数据来增加数据的多样性

随机裁剪


使用别人提供的均值和标准差(imageNet 提供)
([0.485, 0.456, 0.406]这一组平均值是从 imagenet 训练集中抽样算出来的。)

# 制作数据源
data_transforms = {
    'train':
    # 按顺序执行预处理操作
        transforms.Compose([
            # 一般resize都是变成正方形
            # 可以设置成其他大小,越大计算越慢,越小越快
            transforms.Resize([96, 96]),  # 图片大小不一致,这里resize使之一致
            transforms.RandomRotation(45),  # 随机旋转,-45到45之间随机选
            # 随机裁剪后变成 -> [64,64]
            transforms.CenterCrop(64),  # 从中心开始裁剪
            transforms.RandomHorizontalFlip(p=0.5),  # 随机水平翻转 选择一个概率
            transforms.RandomVerticalFlip(p=0.5),  # 随机垂直翻转
            # 这种数据变换(增强)方式用得不多
            # 参数1:亮度,参数2:对比度,参数3:饱和度,参数4:色相
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            # 用的少
            transforms.RandomGrayscale(p=0.025),  # 依据一定的概率将图片转换成灰度图,3通道就是R=G=B
            transforms.ToTensor(),  # 转为tensor
            # 标准化
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 均值,标准差
        ]),
    'valid':
        transforms.Compose([
            transforms.Resize([64, 64]),
            transforms.ToTensor(),
            # 训练集怎么标准化,测试集也怎么标准化
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 均值,标准差
        ])
}

03 数据集与模型选择


batch_size = 128

# 因为文件夹名称(1~x)就是对应的分类(label),所以直接通过取文件夹名称作为标签
# 这里得到文件路径,并且对应上transform处理
image_datasets =
{x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}
dataloaders =
{x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes

print('class names:\n', class_names[:5])
print('datasets:\n', image_datasets)
print('dataloaders:\n', dataloaders)
print('data size:\n', dataset_sizes)

# 读取标签对应的实际name
with open('cat_to_name.json', 'r') as f:
    cat_to_name = json.load(f)

print('cat_to_name:\n', cat_to_name)

# 加载models提供的模型,并直接使用训练好的权重当作初始化参数
# 第一次执行需要下载,可以自己下载然后放到对应目录
model_name = 'resnet'  # 可选 ['resnet','alexnet','vgg','squeezenet','densenet','inception']
# 是否使用模型训练好的特征
feature_extract = True  # 都用别人的特征,先不更新 -> 冻住
# 是否使用GPU
train_on_gpu = torch.cuda.is_available()

if not train_on_gpu:
    print('CUDA is not available. Training on CPU...')
else:
    print('CUDA is available. Training on GPU...')

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

04 迁移学习方法解读

迁移学习:在别人的模型基础上进行微调
把别人的直升机拆开然后研究怎么使,再改进

别人的模型的权重作为我们的初始参数,而且也不一定别人的模型训练过我们的数据集

可以根据自己有的数据集的大小,来考虑要冻住哪些层,比如数据集大,预估可以训练效果不错,那就可以只把别人模型训练出的参数作为初始化权重,而不使用其现成的所有层;如果数据集较小,可以选择冻住大部分别人模型训练好的神经网络层,只训练改变一小部分层

# 是否更新参数
def set_parameter_requires_grad(model: nn.Module, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            # 不更新参数
            # requires_grad表示反向传播时要不要计算它的梯度
            param.requires_grad = False


model_ft = models.resnet18()  # 18层的比较快,条件好的可以选152
print(model_ft)

05 输出层与梯度设置

BatchNorm2d 就是以 Batch 维度进行归一化(标准化),每次卷积后都会进行

avgpool 层,假设输入为 14*14*512,则将 14*14 求平均得 1*512,就是变成一维的

fc 层,输出是 1000(千分类),我们需要修改输出层


06 输出类别个数修改

保存模型就是保存权重、网络结构等

# 输出层改为自己的
def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
    model_ft = models.resnet18(pretrained=use_pretrained)
    # 是否更新参数
    set_parameter_requires_grad(model_ft, feature_extract)

    # 得到最后一层(输出层)的输入元素数量
    num_fits = model_ft.fc.in_features
    # 将输入元素数量和我们自己定义的输出元素数量定义一个新的输出层
    # 并替换原输出层
    model_ft.fc = nn.Linear(num_fits, 102)

    input_size = 64  # 输入大小根据自己的配置
    return model_ft, input_size


# 设置哪些层需要训练
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)

# GPU?CPU?
model_ft = model_ft.to(device)

# 模型保存,名称自拟
filename = 'best.pt'

# 是否训练所有层
params_to_update = model_ft.parameters()
print('Params to learn:')
if feature_extract:
    params_to_update = []
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            params_to_update.append(param)
            print('to_update param:\t', name)
else:
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            print('to_update param:\t', name)

# 此时输出fc.weight fc.bias,因为我们把model的所有参数设置为不变,
# 但是我们自己定义的fc层的参数默认是会改变的

07 优化器与学习率衰减

学习率衰减



# 优化器设置
# params_to_update -> 要更新哪些参数
optimizer_ft = optim.Adam(params_to_update, lr=1e-2)
# 学习率衰减策略: 学习率每7epoch(step_size)衰减成原来的1/10
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)
# 计算损失的(交叉熵)
criterion = nn.CrossEntropyLoss()


# 训练
def train_model(model: nn.Module,
                dataloaders: torch.utils.data.DataLoader,
                criterion,
                optimizer: optim.Optimizer,
                num_epochs=25,
                filename='best.pt'):
    # 记录开始时间
    since = time.time()
    # 记录最好的那一次准确率
    best_acc = 0
    # 放到设备进行训练
    model.to(device)
    # 训练时要打印的指标
    val_acc_history = []
    train_acc_history = []
    valid_losses = []
    train_losses = []
    # 学习率
    # optimizer.param_groups[0]包含很多参数
    LRs = [optimizer.param_groups[0]['lr']]
    # 最好的那次模型,后续会变,先初始化
    # state_dict:模型里的参数
    best_model_tws = copy.deepcopy(model.state_dict())
    # 遍历,训练
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # 训练和验证
        for phase in ['train', 'valid']:
            # 指定模式
            if phase == 'train':
                model.train()
            else:
                model.eval()

            running_loss = 0.0
            running_corrects = 0

            # 数据取个遍(每次只取 1 batch_size)
            for inputs, labels in dataloaders[phase]:
                # 数据放到设备中,
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 梯度清零
                optimizer.zero_grad()
                # 只有训练时才计算梯度
                outputs = model(inputs)
                # 计算损失
                loss = criterion(outputs, labels)
                # val,idx=max(data,axis)
                _, preds = torch.max(outputs, 1)
                # 训练阶段更新权重
                if phase == 'train':
                    # 计算梯度
                    loss.backward()
                    # 更新权重
                    optimizer.step()

                # 计算损失
                running_loss += loss.item() * inputs.size(0)  # 0表示batch_size那个维度
                running_corrects += torch.sum(preds == labels.data)  # 预测结果的最大可能取值是否和真实值一致

            epoch_loss = running_loss / len(dataloaders[phase].dataset)  # 算平均loss
            # running_corrects是预测正确的个数
            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)  # 算准确率

            # 计算本epoch执行了多长时间
            time_elapsed = time.time() - since
            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            # 得到最好的那次模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_tws = copy.deepcopy(model.state_dict())
                state = {
                    'state_dict': model.state_dict(),  # 字典里key就是各层的名字,值是训练好的权重
                    'best_acc': best_acc,
                    'optimizer': optimizer.state_dict(),
                }
                # 保存参数和网络结构
                torch.save(state, filename)
            if phase == 'valid':
                val_acc_history.append(epoch_acc)
                valid_losses.append(epoch_loss)
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)

        print('Optimizer learning rate: {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()
        # 学习率衰减
        # (我们设置了step,但是不用担心,每次调用都会累加次数,
        # 直到达到step值,才会更新学习率)
        scheduler.step()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:.4f}'.format(best_acc))

    # 训练完用最好的一次模型作为最终结果
    model.load_state_dict(best_model_tws)
    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs

08 模型训练方法

# 开始训练 > 这里只训练了输出层
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs =
train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epochs, filename)

09 重新训练全部模型

一般的模型层数: 18 50 101 152

# 再继续训练所有层
for param in model_ft.parameters():
    param.requires_grad = True

# 再训练所有的参数,学习率调小点
optimizer = optim.Adam(model_ft.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 损失函数
criterion = nn.CrossEntropyLoss()

# 加载之前训练好的权重参数
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc']
model_ft.load_state_dict(checkpoint['state_dict'])

# 再次训练 > 训练所有层
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs =
train_model(model_ft, dataloaders, criterion, optimizer, scheduler, num_epochs, filename)

10 测试结果演示分析



# 测试数据预处理
# 取得验证集迭代器
dataiter = iter(dataloaders['valid'])
# 取一批
images, labels = dataiter.next()

model_ft.eval()

if train_on_gpu:
    output = model_ft(images.cuda())
else:
    output = model_ft(images)

# output是预测的每个可能的概率
print('output shape:\n', output.shape)

# 得到概率最大的那个
_, preds_tensor = torch.max(output, 1)

# 转为numpy() -> matplotlib才能它用来画
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())
print('preds:\n', preds)


# 展示预测结果
def im_convert(tensor):
    """展示数据"""

    image = tensor.to('cpu').clone().detach()
    image = image.numpy().squeeze()
    # (3,64,64) -> (64,64,3)
    # 第一位移到最后,因为torch的图像是通道数在第一位,需要转换
    image = image.transpose(1, 2, 0)
    # 从标准化的数据还原初始数据
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    # 将iamge限制在0~1之间
    image = image.clip(0, 1)

    return image


# 画图
fig = plt.figure(figsize=(20, 20))
columns = 4
rows = 2

for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx + 1, xticks=[], yticks=[])
    plt.imshow(im_convert(images[idx]))
    ax.set_title("{} ({})".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),
                 color=("green" if cat_to_name[str(preds[idx])] == cat_to_name[str(labels[idx].item())] else "red"))
plt.show()

06 DataLoader 自定义数据集制作

01 Dataloader 要完成的任务分析

之前我们的数据是文件夹使用标签名命名,但是实际上经常见到的是所有数据放在一个文件夹


同时会提供一个记录图片名称和 label 标签对应关系的文件

还有其他类型的


02 图像数据与标签路径处理




import os
import matplotlib.pyplot as plt
# %matplotlib inline
import numpy as np
import torch
from torch import nn
import torchvision
from torchvision import transforms, models, datasets
import imageio
import time
import warnings

warnings.filterwarnings('ignore')
import random
import sys
import copy
import json
from PIL import Image


# step1: 读取txt文件中的路径和标签
def load_annotations(ann_file):
    data_infos = {}
    with open(ann_file) as f:
        # xxx.png n -> [xxx.png,n]
        # strip -> 去掉\n
        samples = [x.strip().split(' ') for x in f.readlines()]
        for filename, gt_label in samples:
            # xxx.png: n
            data_infos[filename] = np.array(gt_label, dtype=np.int64)
    return data_infos


print('load train txt:\n', load_annotations('./flower_data/train.txt'))

# step2: 分别把数据和标签存到list
img_label = load_annotations('./flower_data/train.txt')
image_name = list(img_label.keys())
label = list(img_label.values())

print('img name:\n', image_name[:5])
print('label:\n', label[:5])

# step3: 将图像路径补充完整
data_dir = './flower_data/'
train_dir = data_dir + '/train_filelist'
valid_dir = data_dir + '/val_filelist'

image_path = [os.path.join(train_dir, img) for img in image_name]
print('img path:\n', image_path[:5])

03 Dataloader 中需要实现的方法分析


别的项目里标签也有可能被预处理

04 使用 Dataloader 加载数据并训练模型




# step4: 将上述操作封装
from torch.utils.data import Dataset, DataLoader


# 自定义Dataset
class FlowerDataset(Dataset):
    def __init__(self, root_dir, ann_file, transform=None):
        self.ann_file = ann_file
        self.root_dir = root_dir
        self.img_label = self.load_annotations()
        # 图片路径list
        self.img = [os.path.join(self.root_dir, img) for img in list(self.img_label.keys())]
        # 标签list
        self.label = [label for label in list(self.img_label.values())]
        # 定义的预处理操作
        self.transform = transform

    def __len__(self):
        return len(self.img)

    # 模型调用时,一个一个id来取,然后组成batch
    def __getitem__(self, idx):
        image = Image.open(self.img[idx])
        label = self.label[idx]
        if self.transform:
            # 预处理
            image = self.transform(image)
        label = torch.from_numpy(np.array(label))
        return image, label

    def load_annotations(self):
        data_infos = {}
        with open(self.ann_file) as f:
            # xxx.png n -> [xxx.png,n]
            # strip -> 去掉\n
            samples = [x.strip().split(' ') for x in f.readlines()]
            for filename, gt_label in samples:
                # xxx.png: n
                data_infos[filename] = np.array(gt_label, dtype=np.int64)
        return data_infos


# step5: 预处理
data_transforms = {
    'train':
    # 按顺序执行预处理操作
        transforms.Compose([
            # 一般resize都是变成正方形
            # 可以设置成其他大小,越大计算越慢,越小越快
            transforms.Resize([96, 96]),  # 图片大小不一致,这里resize使之一致
            transforms.RandomRotation(45),  # 随机旋转,-45到45之间随机选
            # 随机裁剪后变成 -> [64,64]
            transforms.CenterCrop(64),  # 从中心开始裁剪
            transforms.RandomHorizontalFlip(p=0.5),  # 随机水平翻转 选择一个概率
            transforms.RandomVerticalFlip(p=0.5),  # 随机垂直翻转
            # 这种数据变换(增强)方式用得不多
            # 参数1:亮度,参数2:对比度,参数3:饱和度,参数4:色相
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            # 用的少
            transforms.RandomGrayscale(p=0.025),  # 依据一定的概率将图片转换成灰度图,3通道就是R=G=B
            transforms.ToTensor(),  # 转为tensor
            # 标准化
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 均值,标准差
        ]),
    'valid':
        transforms.Compose([
            transforms.Resize([64, 64]),
            transforms.CenterCrop(64),
            transforms.ToTensor(),
            # 训练集怎么标准化,测试集也怎么标准化
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 均值,标准差
        ])
}

# step6: 根据写好class FlowerDataset的dataloader
# 创建时需要传啥是自定义的
train_dataset = FlowerDataset(root_dir=train_dir,
                              ann_file='./flower_data/train.txt',
                              transform=data_transforms['train'])
val_dataset = FlowerDataset(root_dir=valid_dir,
                            ann_file='./flower_data/val.txt',
                            transform=data_transforms['valid'])
# 创建loader
# shuffle 洗牌打乱再取
# batch_size 一批次取多少
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=True)

print('train dataset:\n', len(train_dataset))  # 6552
print('valid dataset:\n', len(val_dataset))  # 818

# step7: 在训练前,先测试看看标签和数据是否对应
# 拿到一个batch的数据和标签
image, label = iter(train_loader).next()
# torch.Size([64, 3, 64, 64])
print('image shape:\n', image.shape)
# torch.Size([64])
print('label shape:\n', label.shape)

# 从数组的形状中删除单维度条目,即把shape中为1的维度去掉
# 经常有1x3x64x64里 1 这种空维度,所以用squeeze压缩(去掉)
sample = image[0].squeeze()  # 这一步可有可无
# [3,64,64] -> [64,64,3]
sample = sample.permute((1, 2, 0)).numpy()
# 标准化的逆向操作,还原到初始
sample *= [0.229, 0.224, 0.225]
sample += [0.485, 0.456, 0.406]
plt.imshow(sample)
plt.show()
print('Label is: {}'.format(label[0].numpy()))

# 验证集
image, label = iter(val_loader).next()
# 验证集
sample = image[0].squeeze()  # 这一步可有可无
# [3,64,64] -> [64,64,3]
sample = sample.permute((1, 2, 0)).numpy()
# 标准化的逆向操作,还原到初始
sample *= [0.229, 0.224, 0.225]
sample += [0.485, 0.456, 0.406]
plt.imshow(sample)
plt.show()
print('Label is: {}'.format(label[0].numpy()))

# step8: 训练
dataloaders = {'train': train_loader, 'valid': val_loader}

# 读取标签对应的实际name
with open('cat_to_name.json', 'r') as f:
    cat_to_name = json.load(f)

print('cat_to_name:\n', cat_to_name)

# 加载models提供的模型,并直接使用训练好的权重当作初始化参数
# 第一次执行需要下载,可以自己下载然后放到对应目录
model_name = 'resnet'  # 可选 ['resnet','alexnet','vgg','squeezenet','densenet','inception']
# 是否使用模型训练好的特征
feature_extract = True  # 都用别人的特征,先不更新 -> 冻住
# 是否使用GPU
train_on_gpu = torch.cuda.is_available()

if not train_on_gpu:
    print('CUDA is not available. Training on CPU...')
else:
    print('CUDA is available. Training on GPU...')

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')


# 是否更新参数
def set_parameter_requires_grad(model: nn.Module, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            # 不更新参数
            # requires_grad表示反向传播时要不要计算它的梯度
            param.requires_grad = False


model_ft = models.resnet18()  # 18层的比较快,条件好的可以选152
print(model_ft)


# 输出层改为自己的
def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
    model_ft = models.resnet18(pretrained=use_pretrained)
    # 是否更新参数
    set_parameter_requires_grad(model_ft, feature_extract)

    # 得到最后一层(输出层)的输入元素数量
    num_fits = model_ft.fc.in_features
    # 将输入元素数量和我们自己定义的输出元素数量定义一个新的输出层
    # 并替换原输出层
    model_ft.fc = nn.Linear(num_fits, num_classes)

    input_size = 64  # 输入大小根据自己的配置
    return model_ft, input_size


# 设置哪些层需要训练
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)

# GPU?CPU?
model_ft = model_ft.to(device)

# 模型保存,名称自拟
filename = 'best.pt'
# 训练轮次
num_epochs = 25

# 是否训练所有层
params_to_update = model_ft.parameters()
print('Params to learn:')
if feature_extract:
    params_to_update = []
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            params_to_update.append(param)
            print('to_update param:\t', name)
else:
    for name, param in model_ft.named_parameters():
        if param.requires_grad == True:
            print('to_update param:\t', name)

# 此时输出fc.weight fc.bias,因为我们把model的所有参数设置为不变,
# 但是我们自己定义的fc层的参数默认是会改变的

# 优化器设置
# params_to_update -> 要更新哪些参数
optimizer_ft = optim.Adam(params_to_update, lr=1e-2)
# 学习率衰减策略: 学习率每7epoch(step_size)衰减成原来的1/10
# gamma是每次减少的倍率 0.1 -> lr=0.01 -> lr*gamma -> lr=0.001
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.1)
# 计算损失的(交叉熵)
criterion = nn.CrossEntropyLoss()


# 训练
def train_model(model: nn.Module,
                dataloaders: torch.utils.data.DataLoader,
                criterion,
                optimizer: optim.Optimizer,
                scheduler,
                num_epochs=25,
                filename='best.pt'):
    # 记录开始时间
    since = time.time()
    # 记录最好的那一次准确率
    best_acc = 0
    # 放到设备进行训练
    model.to(device)
    # 训练时要打印的指标
    val_acc_history = []
    train_acc_history = []
    valid_losses = []
    train_losses = []
    # 学习率
    # optimizer.param_groups[0]包含很多参数
    LRs = [optimizer.param_groups[0]['lr']]
    # 最好的那次模型,后续会变,先初始化
    # state_dict:模型里的参数
    best_model_tws = copy.deepcopy(model.state_dict())
    # 遍历,训练
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # 训练和验证
        for phase in ['train', 'valid']:
            # 指定模式
            if phase == 'train':
                model.train()
            else:
                model.eval()

            running_loss = 0.0
            running_corrects = 0

            # 数据取个遍(每次只取 1 batch_size)
            for inputs, labels in dataloaders[phase]:
                # 数据放到设备中,
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 梯度清零
                optimizer.zero_grad()
                # 只有训练时才计算梯度
                outputs = model(inputs)
                # 计算损失
                loss = criterion(outputs, labels)
                # val,idx=max(data,axis)
                _, preds = torch.max(outputs, 1)
                # 训练阶段更新权重
                if phase == 'train':
                    # 计算梯度
                    loss.backward()
                    # 更新权重
                    optimizer.step()

                # 计算损失
                running_loss += loss.item() * inputs.size(0)  # 0表示batch_size那个维度
                running_corrects += torch.sum(preds == labels.data)  # 预测结果的最大可能取值是否和真实值一致

            epoch_loss = running_loss / len(dataloaders[phase].dataset)  # 算平均loss
            # running_corrects是预测正确的个数
            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)  # 算准确率

            # 计算本epoch执行了多长时间
            time_elapsed = time.time() - since
            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            # 得到最好的那次模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_tws = copy.deepcopy(model.state_dict())
                state = {
                    'state_dict': model.state_dict(),  # 字典里key就是各层的名字,值是训练好的权重
                    'best_acc': best_acc,
                    'optimizer': optimizer.state_dict(),
                }
                # 保存参数和网络结构
                torch.save(state, filename)
            if phase == 'valid':
                val_acc_history.append(epoch_acc)
                valid_losses.append(epoch_loss)
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)

        print('Optimizer learning rate: {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()
        # 学习率衰减
        # (我们设置了step,但是不用担心,每次调用都会累加次数,
        # 直到达到step值,才会更新学习率)
        scheduler.step()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:.4f}'.format(best_acc))

    # 训练完用最好的一次模型作为最终结果
    model.load_state_dict(best_model_tws)
    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs


# 开始训练 > 这里只训练了输出层
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs =
train_model(model_ft, dataloaders, criterion, optimizer_ft, scheduler, num_epochs, filename)

# 再继续训练所有层
for param in model_ft.parameters():
    param.requires_grad = True

# 再训练所有的参数,学习率调小点
optimizer = optim.Adam(model_ft.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 损失函数
criterion = nn.CrossEntropyLoss()

# 加载之前训练好的权重参数
checkpoint = torch.load(filename)
best_acc = checkpoint['best_acc']
model_ft.load_state_dict(checkpoint['state_dict'])

# 再次训练 > 训练所有层
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs =
train_model(model_ft, dataloaders, criterion, optimizer, scheduler, num_epochs, filename)

# 测试数据预处理
# 取得验证集迭代器
dataiter = iter(dataloaders['valid'])
# 取一批
images, labels = dataiter.next()

model_ft.eval()

if train_on_gpu:
    output = model_ft(images.cuda())
else:
    output = model_ft(images)

# output是预测的每个可能的概率
print('output shape:\n', output.shape)

# 得到概率最大的那个
_, preds_tensor = torch.max(output, 1)

# 转为numpy() -> matplotlib才能它用来画
preds = np.squeeze(preds_tensor.numpy()) if not train_on_gpu else np.squeeze(preds_tensor.cpu().numpy())
print('preds:\n', preds)


# 展示预测结果
def im_convert(tensor):
    """展示数据"""

    image = tensor.to('cpu').clone().detach()
    image = image.numpy().squeeze()
    # (3,64,64) -> (64,64,3)
    # 第一位移到最后,因为torch的图像是通道数在第一位,需要转换
    image = image.transpose(1, 2, 0)
    # 从标准化的数据还原初始数据
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    # 将iamge限制在0~1之间
    image = image.clip(0, 1)

    return image


# 画图
fig = plt.figure(figsize=(20, 20))
columns = 4
rows = 2

for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx + 1, xticks=[], yticks=[])
    plt.imshow(im_convert(images[idx]))
    ax.set_title("{} ({})".format(cat_to_name[str(preds[idx])], cat_to_name[str(labels[idx].item())]),
                 color=("green" if cat_to_name[str(preds[idx])] == cat_to_name[str(labels[idx].item())] else "red"))
plt.show()

07 LSTM 文本分类实战

01 数据集与任务目标分析

文本数据的预处理很麻烦

一堆特殊字符

02 文本数据处理基本流程分析

分字或分词看自己有什么语料表,分字分词的编号表、向量表都是用现成的,因为自己搞需要大量数据、时间、算力

  1. 分字分词都可以,谷歌是分字

  1. 构建字或词对应的编号表

  1. 词的映射表,因为字或词是有含义的,如果只用编号表达,无法表达出不同的含义,所以构建词向量



03 命令行参数与 DEBUG

语料表,内含 5000 词,每个词 300 个向量


断点

往下跳一个

跳到函数里


04 训练模型所需基本配置参数分析

预训练 embedding

词向量,每层都是全连接层,每次看前 n 个词预测一个输出词,我们一般只需要最后一个词向量的预测值


画圈的这几个都可以改,其他的不行

pad_size:每个文本长度不同,可以指定一个统一长度,超出部分去除,少的部分补值(0),但是不能设置太长(1000?),NLP 的长文本预测是大难题

05 预料表与字符切分


可以自己决定多少层,设置太大容易过拟合,而且训练慢

设置随机数种子,因为每次训练得到的参数不同,训练用的数据是随机的,但是随机种子可以让随机数相同,就可以对比参数





06 字符预处理转换 ID

读取文件,tqdm 是进度条



PAD 字符原来填充不够 pad_size 长的词
UNK 字符原来替代语料表中没有,但是数据里有的未知字符


记录词对应的编号,如果没有,则用 UNK

句子转为词的编号序列

07 LSTM 网络结构基本定义

构建 dataloader



训练步骤


指定了双向 bidirectional,则每层输出两个结果,拼在一起,这里 hidden_size 是 128,拼起来就是 256



08 网络模型预测结果输出






09 模型训练任务与总结






08 PyTorch 框架 Flask 部署例子

01 基本结构与训练好的模型加载

import os
import json
import flask
import torch
import torch.nn.functional as F
from PIL import Image
from torch import nn
from torchvision import transforms, models, datasets
from torch.autograd import Variable

# 初始化flask app
app = flask.Flask(__name__)
model = None
use_gpu = False


# 加载模型(每次请求就加载)
def load_model():
    '''
    load the pre-trained model, you can use your model just as easily.
    :return:
    '''
    global model
    # 直接加载官方工具包提供的训练好的模型(代码自动下载),括号内参数为是否下载模型以及对应配置信息
    model = models.resnet18()
    # 得到输出层的需要的输入的shape
    num_ftrs = model.fc.in_features
    # 修改输出层
    model.fc = nn.Sequential(nn.Linear(num_ftrs, 102))

    print(model)
    checkpoint = torch.load('best.pt')
    # checkpoint = torch.load('best.pth')
    # 加载模型参数
    model.load_state_dict(checkpoint['state_dict'])
    # 转为测试模式(不更新参数)
    model.eval()
    # 是否使用gpu
    if use_gpu:
        model.cuda()

02 服务端处理与预测函数


import io
import os
import json
import flask
import torch
import torch.nn.functional as F
from PIL import Image
from torch import nn
from torchvision import transforms, models, datasets
from torch.autograd import Variable

# 初始化flask app
app = flask.Flask(__name__)
model = None
use_gpu = False


# 加载模型
def load_model():
    '''
    load the pre-trained model, you can use your model just as easily.
    :return:
    '''
    global model
    # 直接加载官方工具包提供的训练好的模型(代码自动下载),括号内参数为是否下载模型以及对应配置信息
    model = models.resnet18()
    # 得到输出层的需要的输入的shape
    num_ftrs = model.fc.in_features
    # 修改输出层
    model.fc = nn.Sequential(nn.Linear(num_ftrs, 102))
    # 比我们训练的模型多出一层sequential封装,内部fc变成fc.0
    # 我们训练的模型是直接改输出层为nn.Linear,所以这里要和它一致 fc.xxx,不需要加sequential
    # model.fc = nn.Sequential(nn.Linear(num_ftrs, 102))

    print(model)
    checkpoint = torch.load('best.pt')
    # checkpoint = torch.load('best.pth')
    # 加载模型参数
    model.load_state_dict(checkpoint['state_dict'])
    # 转为测试模式(不更新参数)
    model.eval()
    # 是否使用gpu
    if use_gpu:
        model.cuda()


# 数据预处理
# 请求的数据应该经过和训练时一样的预处理操作
def prepare_image(image, target_size):
    '''
    Do image preprocessing before prediction on any data.
    :param image: original image
    :param target_size: target image size
    :return: preprocessed image
    '''
    # 不同模型需要的image格式不同,但需要统一RGB格式
    if image.mode != 'RGB':
        image = image.convert('RGB')
    # Resize the input image and preprocess it.
    # 根据使用的模型的需要将输入图片的尺寸修改,并转为tensor
    image = transforms.Resize(target_size)(image)
    image = transforms.ToTensor()(image)

    # Convert to Torch.Tensor and normalize. mean & std
    # (RGB三通道) 这里的参数和数据集中是对应的,训练过程中一致
    image = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(image)

    # 我们在训练时是[batch,channels,size,size]的数据shape,所以要增加一个batch维度
    # Add batch_size axis. 增加一个维度,用于按batch测试,本次这里一次测试一张
    image = image[None]
    if use_gpu:
        image = image.cuda()
    return Variable(image, volatile=True)  # 不需要求异


# 开启服务
@app.route('/predict', methods=['POST'])
def predict():
    # Initialize the data dictionary that will be returned from the view
    # 做一个标签,刚开始无图像传入时为false,传入后为true
    data = {'success': False}

    # 如果收到请求
    if flask.request.method == 'POST':
        # 判断是否为图像
        if flask.request.files.get("image"):
            # Read the image in PIL format
            # 将收到的图像读取
            image = flask.request.files['image'].read()
            image = Image.open(io.BytesIO(image))  # 二进制数据

            # 预处理
            image = prepare_image(image, target_size=(64, 64))

            # 预测概率
            preds = F.softmax(model(image), dim=1)
            # topk: 找出传入数据中,最高的k个值
            # 如果使用gpu,则图像在gpu里,.cpu()将其加载,然后才能转numpy(),方便打印
            res = torch.topk(preds.cpu().data, k=3, dim=1)
            print('res:\n', res)
            res = (res[0].cpu().numpy(), res[1].cpu().numpy())
            print('res:\n', res)

            # 将data字典增加一个key,val,val为list
            data['predictions'] = list()

            # loop over the results and add item to the list of returned predictions
            for prob, label in zip(res[0][0], res[1][0]):
                # laber_name = idx2label[str(label)]
                r = {'label': str(label), 'probability': float(prob)}
                # 预测结果添加到data
                data['predictions'].append(r)

            # Indicate that the request was as success.
            data['success'] = True
    # 最终结果返回
    return flask.jsonify(data)


if __name__ == '__main__':
    print('Loading PyTorch model and Flask starting server ...')
    print('Please wait until server has fully started')
    # 加载模型
    load_model()
    # 开启服务
    app.run(port='5012')

03 基于 Flask 测试模型预测结果

import requests
import argparse

# url
flask_url = 'http://127.0.0.1:5012/predict'


def predict_result(image_path):
    # 打开图片
    image = open(image_path, 'rb').read()
    # 请求携带的参数
    payload = {'image': image}
    # request发给server
    r = requests.post(flask_url, files=payload).json()

    # 成功返回
    if r['success']:
        # 输出
        for (i, result) in enumerate(r['predictions']):
            print('{}, {}: {:.4f}'.format(i + 1, result['label'],
                                          result['probability']))

    # 失败
    else:
        print('Request failed')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Classification demo')
    parser.add_argument('--file', default='./flower_data/train_filelist/image_06998.jpg',
                        type=str, help='test image file')

    args = parser.parse_args()
    predict_result(args.file)

04 MMLAB 实战系列

05 Opencv 图像处理框架实战

01 课程简介与环境配置

01 课程简介

02 Python 与 Opencv 配置安装

2018.12 他使用 py3.7,opencv 使用 3.4.1.15(因为他使用的一些算法在 3.4.2 后申请了专利,无法再使用)

pip install opencv-python==3.4.1.15
pip install opencv-contrib-python==3.4.1.15

03 Notebook 与 IDE 环境

02 图像基本操作

01 计算机眼中的图像

像素点的值表示亮度(灰色图)

RGB 表示有 3 个通道,彩色图

import cv2  # opencv默认读取格式BGR
import matplotlib.pyplot as plt
import numpy as np

# %matplotlib inline

img = cv2.imread('./data/cat.jpg')
print('img:\n', img)

# 图像显示,也可以创建多个窗口
cv2.imshow('image', img)
# 等待时间,毫秒级,0表示任意键终止
cv2.waitKey(0)
cv2.destroyAllWindows()


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# (414, 500, 3)
print('img shape:\n', img.shape)

# 读取并转为灰度图(实践中经常这样干)
img = cv2.imread('data/cat.jpg', cv2.IMREAD_GRAYSCALE)
print('img:\n', img)

cv_show('gray cat', img)

# 保存
cv2.imwrite('mycat.jpg', img)

print('img type:\n', type(img))
print('img size:\n', img.size)
print('img dtype:\n', img.dtype)

02 视频的读取与处理

# 数据读取-视频
# VideoCapture可以读取摄像头,用数字0,1等指定设备
# 如果是视频文件,指定路径即可
vc = cv2.VideoCapture('data/test.mp4')
# 检查是否正确打开
if vc.isOpened():
    open, frame = vc.read()
else:
    open = False

while open:
    # 读取一帧
    ret, frame = vc.read()
    if frame is None:
        break
    # if ret==True:
    if ret:
        # 转灰度图
        # 参数1是图,参数2是转换方法
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        cv2.imshow('result', gray)
        # 等待10毫秒或按下esc就退出
        # 因为在循环,所以效果就是每10毫秒切图,像在播放视频一样
        # 如果指定为1,则表示根据处理器速度来调整关闭速度
        if cv2.waitKey(10) & 0xFF == 27:
            break
vc.release()
cv2.destroyAllWindows()

03 ROI 区域

# 截取部分图像数据
img = cv2.imread('data/cat.jpg')
cat = img[0:200, 0:200]
cv_show('cat', cat)

# 颜色通道提取
b, g, r = cv2.split(img)
print('b:\n', b)
# (414, 500)
print('b shape:\n', b.shape)

# 合并通道
img = cv2.merge((b, g, r))
#  (414, 500, 3)
print('img shape:\n', img.shape)

# opencv的图像格式: [B,G,R]
# 只保留R
cur_img = img.copy()
cur_img[:, :, 0] = 0
cur_img[:, :, 1] = 0
cv_show('R', cur_img)
# 只保留G
cur_img = img.copy()
cur_img[:, :, 0] = 0
cur_img[:, :, 2] = 0
cv_show('R', cur_img)
# 只保留B
cur_img = img.copy()
cur_img[:, :, 1] = 0
cur_img[:, :, 2] = 0
cv_show('R', cur_img)

04 边界填充

# 边界填充
top_size, bottom_size, left_size, right_size = (50, 50, 50, 50)

# 复制法: 复制最边缘的像素
replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
# 反射法: 两边进行复制
reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT)
# 反射法: 最边缘的像素为轴反射
reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT_101)
# 外包装法
wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_WRAP)
# 常量法: 常量值填充
constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_CONSTANT,
                              value=0)

import matplotlib.pyplot as plt

plt.subplot(231), plt.imshow(img, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')

plt.show()

05 数值计算

# 数值计算
img_cat = cv2.imread('data/cat.jpg')
img_dog = cv2.imread('data/dog.jpg')

# 每个像素点+10
img_cat2 = img_cat + 10
print('img cat:\n', img_cat[:5, :, 0])
print('img cat 2:\n', img_cat2[:5, :, 0])

# 相当于加完 % 256
img_cat_plus = (img_cat + img_cat2)[:5, :, 0]
print('img cat plus:\n', img_cat_plus)

# 相加,到顶就取最大值
img_cat_add = cv2.add(img_cat, img_cat2)[:5, :, 0]
print('img cat add:\n', img_cat_add)

# 图像融合
# operands could not be broadcast together with shapes (414,500,3) (429,499,3)
# shape要一样才能计算
# img_cat + img_dog

print('img cat shape:\n', img_cat)
print('img dog shape:\n', img_dog)

img_dog = cv2.resize(img_dog, (500, 414))
print('img dog shape:\n', img_dog.shape)  # (414, 500, 3)

# 融合
# 相当于 res = w1*img_cat + w2*img_dog + b
# 这里面: w1=0.4 w2=0.6 b=0
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
plt.imshow(res)
plt.show()

# (0,0)表示不指定
# fx表示变换后x为原来的几倍
# fy表示变换后y为原来的几倍
res = cv2.resize(img, (0, 0), fx=3, fy=1)
plt.imshow(res)
plt.show()

res = cv2.resize(img, (0, 0), fx=1, fy=3)
plt.imshow(res)
plt.show()

03 阈值与平滑处理

01 图像阈值

import cv2
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')
import numpy as np

# 读取并转为灰度图(实践中经常这样干)
img_gray = cv2.imread('data/cat.jpg', cv2.IMREAD_GRAYSCALE)
print('img:\n', img_gray)

# 参数1: 输入图(只能灰度图)
# 参数2: 阈值(一般用255/2)
# 参数3: 超过或小于(根据参数5决定)时赋予的值
# 参数4: 二值化操作的类型(5种)
# THRESH_BINARY: 大于阈值(亮度)部分取最大值,小于的取0
_, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
# THRESH_BINARY_INV: 和THRESH_BINARY相反
_, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
# THRESH_TRUNC: 大于阈值的部分设为阈值(128->127),其他不变
_, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
# THRESH_TOZERO: 大于阈值部分不变,小于的设为0
_, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
# THRESH_TOZERO_INV: 和THRESH_TOZERO相反
_, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
imgs = [img_gray, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(imgs[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

02 图像平滑处理


# 图像平滑处理
# 我们的原图有很多小圆点,希望尽量去除
img = cv2.imread('data/lenaNoise.png')

cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 均值滤波
# 简单的平均卷积操作
blur = cv2.blur(img, (3, 3))  # 3,3是卷积核大小

cv2.imshow('blur', blur)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 方框滤波
# 基本和均值一模一样,可以选择归一化
# 参数2: 通道数(-1,保持原本的数量)
# 参数4: 是否归一化(是,则和blur方法一模一样)
box = cv2.boxFilter(img, -1, (3, 3), normalize=True)

cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 基本和blur一样,如果不选归一化,容易越界(取255(亮度))
box = cv2.boxFilter(img, -1, (3, 3), normalize=False)

cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()

03 高斯与中值滤波


# 高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布的,相当于更重视中间的
gaussian = cv2.GaussianBlur(img, (5, 5), 1)

cv2.imshow('gaussian', gaussian)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 中值滤波
# 相当于用中值替代
# 参数2: 选从小到大排的中间几个值作为中值
median = cv2.medianBlur(img, 5)

cv2.imshow('median', median)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 展示所有的
res = np.hstack((blur, gaussian, median))
cv2.imshow('median vs average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()

04 图像形态学操作

01 腐蚀操作

把颜色不统一的点腐蚀掉

import cv2
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')
import numpy as np

# 读取
# 这个图片是一大片黑色背景和白色字,是二值图,形态学操作的图通常是二值图
img = cv2.imread('data/dige.png')  # 这张图有很多毛刺
print('img:\n', img)


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


cv_show('img', img)

# 腐蚀操作
kernel = np.ones((5, 5), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)

# 毛刺没了,线条变细了
cv_show('erosion', erosion)

pie = cv2.imread('data/pie.png')

cv_show('pie', pie)

# 核越大,腐蚀的可能性变大
# iteration是腐蚀的次数
kernel = np.ones((30, 30), np.uint8)
erosion_1 = cv2.erode(pie, kernel, iterations=1)
erosion_2 = cv2.erode(pie, kernel, iterations=2)
erosion_3 = cv2.erode(pie, kernel, iterations=3)
res = np.hstack((erosion_1, erosion_2, erosion_3))
cv_show('res', res)

02 膨胀操作

# 膨胀操作,类似于腐蚀的反操作
img = cv2.imread('data/dige.png')
print('img:\n', img)

cv_show('img', img)

kernel = np.ones((3, 3), np.uint8)
dige_erosion = cv2.erode(img, kernel, iterations=1)

cv_show('erosion', erosion)

kernel = np.ones((3, 3), np.uint8)
dige_dilate = cv2.dilate(dige_erosion, kernel, iterations=1)

# 字变粗了,但是毛刺没有回来
cv_show('dilate', dige_dilate)

pie = cv2.imread('data/pie.png')

kernel = np.ones((30, 30), np.uint8)
dilate_1 = cv2.dilate(pie, kernel, iterations=1)
dilate_2 = cv2.dilate(pie, kernel, iterations=2)
dilate_3 = cv2.dilate(pie, kernel, iterations=3)
res = np.hstack((dilate_1, dilate_2, dilate_3))
cv_show('res', res)

03 开运算与闭运算

# 开运算和闭运算
# 开:先腐蚀,再膨胀 -> 毛刺去了,图像粗细不变
img = cv2.imread('data/dige.png')

kernel = np.ones((5, 5), np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

cv_show('opening', opening)

# 闭: 先膨胀,再腐蚀 -> 毛刺还在(而且看上去还粗了一点)
img = cv2.imread('data/dige.png')

kernel = np.ones((5, 5), np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

cv_show('closing', closing)

04 梯度计算

# 梯度(变化率) = 膨胀 - 腐蚀 (减法)
pie = cv2.imread('data/pie.png')
kernel = np.ones((7, 7), np.uint8)
dilate = cv2.dilate(pie, kernel, iterations=5)
erosion = cv2.erode(pie, kernel, iterations=5)

res = np.hstack((dilate, erosion))

cv_show('res', res)

gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel)

cv_show('gradient', gradient)

05 礼帽与黑帽

# 礼帽与黑帽
# 礼帽: 原始输入 - 开运算结果 -> 带刺 - 不带刺 = 刺
# 黑帽:闭运算 - 原始输入 -> 带刺 - 带刺 = 原始的轮廓(或多的刺?)

# 礼貌
img = cv2.imread('data/dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

cv_show('tophat', tophat)

# 黑帽
img = cv2.imread('data/dige.png')
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

cv_show('blackhat', blackhat)

05 图像梯度计算

01 Sobel 算子

在这里的样例(黑白饼图),梯度相当于在检测哪里有梯度,也就是边界


Gx 相当于在算右边到左边的距离,计算左右边界的差异值
p6 比起 p3 和 p9 离中心点 p5 更近,所以是 2。

Gy 就是下减上

import cv2

import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')
import numpy as np


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread('pie.png', cv2.IMREAD_GRAYSCALE)
cv_show('img', img)

# 参数1: 图像
# 参数2: 输出图像的深度
# 参数3/4: 分别表示水平和竖直方向
# 参数ksize: sobel算子的大小
sobelX = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
# 显示一个半圆圈
cv_show('sobelX', sobelX)

02 梯度计算方法

# 白到黑是正数,黑到白(右边黑的减左边白的,黑的亮度值小于白的)是负数,而所有负数会被截断成0,所以要取绝对值

# 是 8 位图类型,如果结果也是该类型,那么所有负数会自动截断为 0,发生信息丢失。
# 所以,为了避免信息丢失,我们在计算时使用更高的数据类型 cv2.CV_64F,
# 再通过取绝对值将其映射为 cv2.CV_8U(8 位图)类型

# CV_64F表示一种可以表示负数的形式(64位浮点数)
sobelX = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelX = cv2.convertScaleAbs(sobelX)
# 显示是一个圆圈
cv_show('sobelx', sobelX)

sobelY = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobelY = cv2.convertScaleAbs(sobelY)
cv_show('sobely', sobelY)

# 分别计算x和y,再求和 -> 两个圆圈的空隙多了细线
sobelxy = cv2.addWeighted(sobelX, 0.5, sobelY, 0.5, 0)
cv_show('sobelxy', sobelxy)

# 不建议直接计算
sobelxy = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show('sobelxy', sobelxy)

img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)
cv_show('img', img)
sobelX = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobelX = cv2.convertScaleAbs(sobelX)
sobelY = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobelY = cv2.convertScaleAbs(sobelY)
sobelxy = cv2.addWeighted(sobelX, 0.5, sobelY, 0.5, 0)
# 一种线稿素描高光的感觉
cv_show('sobelxy', sobelxy)

03 scharr 与 lapkacian 算子

scharr 和 sobel 基本一致,只是用的数值更大,而 lapkacian 则类似二阶导,而 scharr 和 sobel
这种类似一阶导,二阶导是一阶导的变化率,对变化更敏感,但是也对噪声更敏感,所以效果不怎么好,通常和其他方法一起使用

相当于在算中间点和边缘的差,p2+p4+p6+p8-4p5

# 不同算子的差异
img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)

scharrx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(img, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)

laplacian = cv2.Laplacian(img, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)

res = np.hstack((sobelxy, scharrxy, laplacian))
cv_show('res', res)

06 边缘检测

01 Canny 边缘检测流程

目标检测可能有多个推测结果,就选择其中最大可能的,抑制(去掉)其他的



02 非极大值抑制



03 边缘检测效果

import cv2

import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')
import numpy as np


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)

# 80~150的保留的线条比50~100的多,
# 因为下界取值范围更大,亮度(值)要更大才能被检测,小于的就被舍弃
# 而不同的上界,则不会决定检测到的多和少,因为超过的会设为边界值
v1 = cv2.Canny(img, 80, 150)
v2 = cv2.Canny(img, 50, 100)

res = np.hstack((v1, v2))
cv_show('res', res)

img = cv2.imread('car.png', cv2.IMREAD_GRAYSCALE)

v1 = cv2.Canny(img, 120, 250)
v2 = cv2.Canny(img, 50, 100)

res = np.hstack((v1, v2))
cv_show('res', res)

07 图像金字塔与轮廓检测

01 图像金字塔定义

特征提取整个金字塔,让图像信息采样更全


从下到上,越来越小,是向下采样


02 金字塔制作方法

import cv2 as cv
import numpy as np


def cv_show(name, img):
    cv.imshow('image', img)
    cv.waitKey(0)
    cv.destroyAllWindows()


img = cv.imread('AM.png')
cv_show('img', img)
# (442, 340, 3)
print('img shape:\n', img.shape)

# 高斯金字塔
up = cv.pyrUp(img)
cv_show('up', up)
# (884, 680, 3)
print('up shape:\n', up.shape)

down = cv.pyrDown(img)
cv_show('down', down)
# (221, 170, 3)
print('down shape:\n', down.shape)

up2 = cv.pyrUp(up)
cv_show('up2', up2)
# (1768, 1360, 3)
print('up2 shape:\n', up2.shape)

# 如果上采样又下采样会怎样?
# 上采样期间用0填充,下采样损失偶数行列
up = cv.pyrUp(img)
up_down = cv.pyrDown(up)

up_down = np.hstack((img, up_down))
cv_show('up-down', up_down)

# 拉普拉斯金字塔
down = cv.pyrDown(img)
down_up = cv.pyrUp(down)
l_l = img - down_up
cv_show('l_l', l_l)

03 轮廓检测方法

最常用的


import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread('car.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 通过阈值来二值化 -> 可以更好地进行边缘检测
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show('thresh', thresh)

# 检测函数
# binary: 二值化图像,
# contours: 保存的轮廓信息,
# hierarchy: 层级
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

04 轮廓检测结果

import cv2
import numpy as np


def cv_show(name, img):
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 通过阈值来二值化 -> 可以更好地进行边缘检测
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show('thresh', thresh)

# 检测函数
# binary: 二值化图像,
# contours: 保存的轮廓信息,
# hierarchy: 层级
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# 绘制轮廓

# 传入绘制图像、轮廓、轮廓索引、颜色模式、线条厚度
# 需要用copy,否则原图会改变
draw_img = img.copy()
# 颜色模式(B,G,R) -> (0, 0, 255) -> R
# 线条厚度 2
# 轮廓索引:多个轮廓时,选择哪个轮廓显示,-1表示所有
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
cv_show('res', res)

draw_img = img.copy()
# 颜色模式(B,G,R) -> (0, 0, 255) -> R
# 线条厚度 2
res = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2)
cv_show('res', res)

05 轮廓特征与近似

将曲线两头连接,取和曲线中一点最近的线作为曲线的近似

把曲线分成尽可能细的线段

# 轮廓特征
# 取出第一个轮廓
cnt = contours[0]
# 面积
area = cv2.contourArea(cnt)
# 周长,True表示闭合的
length = cv2.arcLength(cnt, True)

# 8500.5
print('area cnt:\n', area)
# 437.9482651948929
print('len cnt:\n', length)

# 对轮廓进行各种操作是为了得到更多特征

# 轮廓近似
img = cv2.imread('contours2.png')

# 灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 阈值
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 轮廓检测
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

draw_img = img.copy()
# 画轮廓
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
cv_show('res', res)

# 调整w,越大,近似后越接近一条直线,越小越接近原始轮廓
w = 0.1
epsilon = w * cv2.arcLength(cnt, True)
# 轮廓近似
# epsilon:近似精度,一般为周长的倍数
# epsilon:Parameter specifying the approximation accuracy.
#          This is the maximum distance between the original curve and its approximation
# true表示是闭合的
approx = cv2.approxPolyDP(cnt, epsilon, True)

draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show('res', res)

# 边界矩形
img = cv2.imread('contours.png')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]

# 得到轮廓矩形的参数
x, y, w, h = cv2.boundingRect(cnt)
# 创建矩形轮廓
img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv_show('img', img)

# 轮廓面积
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
# 0.5154317244724715
print(f'轮廓面积与边界矩形比:\n {extent}')

# 外接圆
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, center, radius, (0, 255, 0), 2)
cv_show('img', img)

06 模板匹配方法

在一张图中寻找与给定图最相似的区域





# 模板匹配
img = cv2.imread('lena.jpg', 0)
template = cv2.imread('face.png', 0)
# h 166 w 164
h, w = template.shape[:2]
print(f'h: {h}\nw: {w}')

# (263, 263)
print(f'img shape:\n{img.shape}')
# (166, 134)
print(f'template shape:\n{template.shape}')

# 加NORMAL表示有归一化,比较推荐这种
methods = [
    'cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
    'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED'
]

# 匹配
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF_NORMED)
print(f'res shape:\n{res.shape}')  # (98, 130)

# 得到最小、大值和最小、大值的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(f'min val:\n{min_val}\nmax val:\n{max_val}\nmin loc:\n{min_loc}\nmax loc:\n{max_loc}')

07 匹配效果展示


# 得到模板(小图)的宽高
h, w = template.shape[:2]
for meth in methods:
    img2 = img.copy()

    # 匹配方法的真值(根据str,寻找指定变量)
    # eval: Evaluate the given source in the context of globals and locals.
    method = eval(meth)
    # print(method)

    # 匹配
    res = cv2.matchTemplate(img, template, method)
    # 得到匹配结果数据
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

    # 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMAL取最小值
    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)

    # 画矩形到原图上
    cv2.rectangle(img2, top_left, bottom_right, 255, 2)

    plt.subplot(121), plt.imshow(res, cmap='gray')
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
    plt.subplot(122), plt.imshow(img2, cmap='gray')
    plt.xticks([]), plt.yticks([])  # 隐藏坐标轴
    plt.suptitle(meth)
    plt.show()

# 匹配多个对象
img_bgr = cv2.imread('mario.jpg')
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.jpg', 0)
h, w = template.shape[:2]

res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于80%的坐标
loc = np.where(res >= threshold)
print(f'loc:\n{loc}')

for pt in zip(*loc[::-1]):
    bottom_right = (pt[0] + w, pt[1] + h)
    # 在原图里画矩形
    cv2.rectangle(img_bgr, pt, bottom_right, (0, 0, 255), 2)

cv_show('img_bgr', img_bgr)

08 直方图与傅里叶变换

01 直方图定义



import cv2  # bgr
import numpy as np
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


img = cv2.imread('cat.jpg', 0)  # 0表示灰度图
# 参数1:图像(一般是灰度图)
# 参数2:如果参数1传入灰度图,则指定0,如果是彩图,则可以指定[0],[1],[2],分别为BGR通道
# 参数3:掩膜图像
# 参数4:BIN的数量
# 参数5:像素值范围
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
print(f'hist shape:\n{hist.shape}')  # (256, 1)

# ravel 展平成一维
plt.hist(img.ravel(), 256)
plt.show()

img = cv2.imread('cat.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
    histr = cv2.calcHist([img], [i], None, [256], [0, 256])
    plt.plot(histr, color=col)
    plt.xlim([0, 256])

plt.show()

# mask(掩码)操作
# 创建mask
mask = np.zeros(img.shape[:2], np.uint8)
# (414, 500)
print(f'mask shape:\n{mask.shape}')
# 设置白色区域(不会遮挡)
mask[100:300, 100:400] = 255
cv_show('mask', mask)

img = cv2.imread('cat.jpg', 0)
cv_show('img', img)

# 与操作
masked_img = cv2.bitwise_and(img, img, mask=mask)
cv_show('masked img', masked_img)

hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])

plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0, 255])
plt.show()

02 均衡化原理



主要用累积概率,累积概率就是这个词的概率加上前面的词的概率
根据函数映射的意思是,255*累积概率

03 均衡化效果

均衡化后看起来整体更亮了,但是有些地方可能丢失细节(也更可能被噪音影响)

# 直方图均衡化
img = cv2.imread('lena.jpg', 0)
plt.hist(img.ravel(), 256)
plt.show()

# 均衡化
equ = cv2.equalizeHist(img)
plt.hist(equ.ravel(), 256)
plt.xticks([0, 255])
plt.show()

res = np.hstack((img, equ))
cv_show('res', res)

希望可以分区域进行均衡化

# 自适应直方图均衡化
# 如果单纯分成不同区块进行均衡化,可能导致区块的边界变得明显
# 所以自适应直方图均衡化有内置其他操作
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
res_clahe = clahe.apply(img)
res = np.hstack((img, equ, res_clahe))
cv_show('res', res)

04 傅里叶概述



05 频域变换结果

import numpy as np
import cv2
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')

img = cv2.imread('1.jpg', 0)
# cv主要使用dft() idft(),需要float32类型
img_f32 = np.float32(img)

dft = cv2.dft(img_f32, flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
# 得到灰度图能表示的形式
magnitude_spectrum = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))

# cv2.imwrite('img.jpg', img)
# cv2.imwrite('mag.jpg', magnitude_spectrum)

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

06 低通与高通滤波

转换到频率(进行滤波)可以更简单地对图像进行处理

import numpy as np
import cv2
import matplotlib.pyplot as plt

plt.switch_backend('TkAgg')

img = cv2.imread('lena.jpg', 0)
# cv主要使用dft() idft(),需要float32类型
img_f32 = np.float32(img)

dft = cv2.dft(img_f32, flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)  # 中心计算

# 低通滤波
mask = np.zeros((rows, cols, 2), np.uint8)
# 中心区域置为1,其他区域为0
mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1

# IDFT
# mask之后,相当于只读中间区域
fshift = dft_shift * mask
# 之前dft后有变换,现在变换回去
f_ishift = np.fft.ifftshift(fshift)
# 进行idft -> 这一步传成fshift,图像会变成倒过来的
img_back = cv2.idft(f_ishift)
# 实部和虚部进行处理
img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_back, cmap='gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show()

img = cv2.imread('lena.jpg', 0)
# cv主要使用dft() idft(),需要float32类型
img_f32 = np.float32(img)

dft = cv2.dft(img_f32, flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)  # 中心计算

# 高频滤波
mask = np.ones((rows, cols, 2), np.uint8)
# 中心区域置为0,其他区域为1
mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

# IDFT
# mask之后,相当于去掉中间区域
fshift = dft_shift * mask
# 之前dft后有变换,现在变换回去
f_ishift = np.fft.ifftshift(fshift)
# 进行idft -> 这一步传成fshift,图像会变成倒过来的
img_back = cv2.idft(f_ishift)
# 实部和虚部进行处理
img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_back, cmap='gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show()  # 只剩下一些边界线条

09 项目实战-信用卡数字识别

01 总体流程与方法讲解


可以用模板去匹配

对数字进行外轮廓检测,然后计算轮廓矩形

上面的 my 也是一个轮廓,会用一些操作过滤掉不符合指定大小的轮廓

02 环境配置与预处理

# imutils是在OPenCV基础上的一个封装,
# 达到更为简结的调用OPenCV接口的目的,
# 它可以轻松的实现图像的平移,旋转,缩放,骨架化等一系列的操作。
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
import myutils

# 设置参数
ap = argparse.ArgumentParser()
# 图片路径
ap.add_argument('-i', '--image', required=True, help='path to input image')
# template路径
ap.add_argument('-t', '--template', required=True, help='path to template OCR-A image')
args = vars(ap.parse_args())

FIRST_NUMBER = {
    '3': 'American Express',
    '4': 'Visa',
    '5': 'MasterCard',
    '6': 'Discover Card'
}


def cv_show(name, img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 加载模板文件
img = cv2.imread(args['template'])
cv_show('img', img)

# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref', ref)

# 二值图
# 参数1: 输入图(只能灰度图)
# 参数2: 阈值(一般用255/2)
# 参数3: 超过或小于(根据参数5决定)时赋予的值
# 参数4: 二值化操作的类型(5种)
# THRESH_BINARY: 大于阈值(亮度)部分取最大值,小于的取0
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref', ref)

03 模板处理方法

# 计算轮廓
# RETR_EXTERNAL只检测外轮廓
# CHAIN_APPROX_SIMPLE只保留终点坐标
ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 传入绘制图像、轮廓、轮廓索引、颜色模式、线条厚度
# 需要用copy,否则原图会改变
# 颜色模式(B,G,R) -> (0, 0, 255) -> R
# 线条厚度 2
# 轮廓索引:多个轮廓时,选择哪个轮廓显示,-1表示所有
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)
cv_show('img', img)
print(f'refCnts shape:\n{np.array(refCnts).shape}')
refCnts = myutils.sort_contours(refCnts, method='left-to-right')[0]

digits = {}

# 遍历每个轮廓
for (i, c) in enumerate(refCnts):
    # 计算外接矩形并且resize成合适大小
    (x, y, w, h) = cv2.boundingRect(c)
    roi = ref[y:y + h, x:x + w]
    # resize到大一点
    roi = cv2.resize(roi, (57, 88))

    # 每个数字对应一个模板
    digits[i] = roi

04 输入数据处理方法

# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

# 读取输入图像,预处理
image = cv2.imread(args['image'])
cv_show('image', image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray', gray)

# 礼帽操作,突出更明亮区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat', tophat)

# sobel算子梯度计算
# 参数1: 图像
# 参数2: 输出图像的深度
# 参数3/4: 分别表示水平和竖直方向
# 参数ksize: sobel算子的大小
# ksize=-1相当于用3*3的
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)

# 绝对值
gradX = np.absolute(gradX)
(minval, maxval) = (np.min(gradX), np.max(gradX))
# 归一化
gradX = (255 * ((gradX - minval) / (maxval - minval)))
gradX = gradX.astype('uint8')

print(f'grad shape:\n{np.array(gradX).shape}')
cv_show('gradX', gradX)

# 这些闭操作是为了方便计算数字的外轮廓
# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', gradX)
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)[1]
cv_show('thresh', thresh)

# 再来一个闭操作,因为现在数字里面还有空隙
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
cv_show('thresh', thresh)

# 计算轮廓
# thresh_: 二值化图像,
# threshCnts: 保存的轮廓信息,
# hierarchy: 层级
# RETR_EXTERNAL 只检测外轮廓
# CHAIN_APPROX_SIMPLE 只保留终点坐标
thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

cnts = threshCnts
cur_img = image.copy()
# 绘制轮廓
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)

05 模板匹配得出识别结果

locs = []

# 遍历轮廓
for (i, c) in enumerate(cnts):
    # 计算矩形
    (x, y, w, h) = cv2.boundingRect(c)
    # 长宽比
    ar = w / float(h)

    # 选择合适的区域,根据实际任务判断,
    # 这里的基本都是四个数字一组,所以判断长宽比在这个范围内
    if ar > 2.5 and ar < 4.0:

        if (w > 40 and w < 55) and (h > 10 and h < 20):
            # 符合的留下来
            locs.append((x, y, w, h))

# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x: x[0])

output = []

# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
    # initialize the list of group digits
    groupOutput = []

    # 根据坐标提取每一个组(4个数字)
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    cv_show('group', group)

    # 预处理
    # 阈值处理 二值化
    group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    cv_show('group', group)
    # 计算每一组的轮廓
    group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 轮廓排序
    digitCnts = contours.sort_contours(digitCnts, method='left-to-right')[0]

    # 计算每一组中的每一个数值
    for c in digitCnts:
        # 找到当前数值的外接矩形,resize成合适的大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        cv_show('roi', roi)

        # 计算匹配得分
        scores = []

        # 在模板中计算每一个得分
        # digits 记录了数字和对应的模板
        for (digit, digitROI) in digits.items():
            # 模板匹配
            result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
            # 得到最小、大值和最小、大值的位置
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)

        # 得到合适的数字
        groupOutput.append(str(np.argmax(scores)))

    # 画出来
    cv2.rectangle(image, (gX - 5, gY - 5),
                  (gX + gW + 5, gY + gH + 5),
                  (0, 0, 255), 1)
    cv2.putText(image, ''.join(groupOutput),
                (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX,
                0.65, (0, 0, 255), 2)

    # 得到结果
    # extend 可以将一个序列合并到列表中。
    output.extend(groupOutput)

# 打印结果
print(output)
# print(f'Credit Card Type: {FIRST_NUMBER[]}')
print(f'Credit Card #: {"".join(output)}')
cv_show('image', image)
cv2.imwrite('res.jpg', image)

10 项目实战-文档扫描 OCR 识别

01 整体流程演示



02 文档轮廓提取

06 综合项目-物体检测经典算法实战

01 深度学习经典检测方法概述

01 检测任务中阶段的意义

02 不同阶段算法优缺点分析

03 IOU 指标计算

04 评估所需参数计算

05 map 指标计算

02 YOLO-V1 整体思想与网络架构

01 YOLO 算法整体思路解读

02 检测算法要得到的结果

03 整体网络架构解读

04 位置损失计算

07 图像分割实战

08 行为识别实战

09 2022 论文必备-Transformer 实战系列

01 课程介绍





02 自然语言处理通用框架 BERT 原理解读

01 BERT 任务目标概述

02 传统解决方案遇到的问题

传统 RNN 不能并行计算,因为每一步依赖上一步

attention 机制是根据词的重要程度进行调整,self-attention 是 transformer 提出的

词向量不应该永远不变,而是应该根据上下文不同而改变


03 注意力机制的作用

让计算机自己(通过上下文)寻找应该注意的特征,也就是将上下文信息融入到词向量中

04 self-attention 计算方法


无关时内积为 0(直角),相关时有夹角,越相关内积越大


05 特征分配与 softmax 机制

self-attention 计算词和其他每个词的关系




06 Multi-head 的作用





07 位置编码与多层堆叠

残差这里就是,一条路是 x 输入经过神经网络的各种计算、变换产生输出,再提供一条 x
不经过神经网络,而是直接到达结果处的,这样可以在不确定模型效果的时候,保留原始输入,让总体效果更好 ———— 至少不比原来差

08 transformer 整体架构梳理

编码器和解码器是连接的,encode-decode-attention

解码器通过 q 来查询编码器里的 k、v,从而利用编码器的特征

decoder 端是一个接着一个按顺序出结果的,所以后面的词不能被使用,于是用 mask 机制来防止后续的词被前面的使用来进行预测

最终输出就是 softmax,输出不同可能性的概率,是分类任务



09 BERT 模型训练方法




10 训练实例





03 Transformer 在视觉中的应用 VIT 算法

01 transformer 发家史介绍


02 对图像数据构建 patch 序列

识别前景(主体),并让模型更多地关注主体


03 VIT 整体架构解读

图像是有顺序的,我们也需要这些位置信息,有两种方法

    1. 直接按图片划分的顺序做编号
    1. 或者记录横纵坐标


这个多出来的 0 是分类任务中用的,就是当输入的图像信息被编码后,不是将这些信息一个一个编码,而是用图中这个 0 号 token
去和编码后的信息计算,然后将它们的信息整合到这个 0 号 token 里

04 CNN 遇到的问题与窘境

想要检测这个人像,我们需要很大的感受野,比如一个 3x3 的卷积核,它的感受野就是
3x3,这样肯定不够,就需要不断堆叠卷积,下一层的卷积可以整合上一层卷积的信息,从而有更大的感受野,也就是说,为了更大的感受野,不得不一直堆叠卷积层

而 transformer 就可以有很大的感受野

05 计算公式解读

transformer 需要巨大的数据量(应该是百万级别以上),它不是一步一步学习,而是直接学习全局


06 位置编码与 TNT 模型


这个是在分类任务里的论文,在检测领域用2d的多


07 TNT 模型细节分析






04 VIT 算法模型源码解读

10 图神经网络实战

01 图神经网络基础

01 图神经网络应用领域分析









02 图基本模块定义

11 3D 点云实战

12 目标追踪与姿态估计实战

13 面向深度学习的无人驾驶实战

14 对比学习与多模态任务实战

15 缺陷检测实战

16 行人重识别实战

17 对抗生成网络实战

01 课程介绍







02 对抗生成网络架构原理与实战解析

01 对抗生成网络通俗解释_ev



02 GAN 网络组成_ev

二分类

损失函数决定了网络的走向

03 损失函数解释说明_ev

BCELoss

import torch
from torch import autograd

# 随机的输入值
input = autograd.Variable(torch.tensor([[1.9072, 1.1079, 1.4906],
                                        [-0.6584, -0.0512, 0.7608],
                                        [-0.0614, 0.6583, 0.1095]]), requires_grad=True)
print(input)
print('-' * 100)

from torch import nn

# sigmoid激活函数
m = nn.Sigmoid()
# 需要用sigmoid函数将输入限定在0~1之间(BCELoss的使用前提)
print(m(input))
print('-' * 100)

# 假设的标签
target = torch.FloatTensor([[0, 1, 1], [1, 1, 1], [0, 0, 0]])
print(target)
print('-' * 100)

import math

# 根据loss公式计算
r11 = 0 * math.log(0.8707) + (1 - 0) * math.log((1 - 0.8707))
r12 = 1 * math.log(0.7517) + (1 - 1) * math.log((1 - 0.7517))
r13 = 1 * math.log(0.8162) + (1 - 1) * math.log((1 - 0.8162))

r21 = 1 * math.log(0.3411) + (1 - 1) * math.log((1 - 0.3411))
r22 = 1 * math.log(0.4872) + (1 - 1) * math.log((1 - 0.4872))
r23 = 1 * math.log(0.6815) + (1 - 1) * math.log((1 - 0.6815))

r31 = 0 * math.log(0.4847) + (1 - 0) * math.log((1 - 0.4847))
r32 = 0 * math.log(0.6589) + (1 - 0) * math.log((1 - 0.6589))
r33 = 0 * math.log(0.5273) + (1 - 0) * math.log((1 - 0.5273))

r1 = -(r11 + r12 + r13) / 3
# 0.8447112733378236
r2 = -(r21 + r22 + r23) / 3
# 0.7260397266631787
r3 = -(r31 + r32 + r33) / 3
# 0.8292933181294807
bceloss = (r1 + r2 + r3) / 3
print(bceloss)
print('-' * 100)

loss = nn.BCELoss()
# 0.8000147727101611
print(loss(m(input), target))
print('-' * 100)

# 可以自动将输入进行sigmoid映射
loss = nn.BCEWithLogitsLoss()
print(loss(input, target))

04 数据读取模块_ev

import argparse
import os
import numpy as np
import math

import torchvision.transforms as transforms
from torchvision.utils import save_image

from torch.utils.data import DataLoader
from torchvision import datasets
from torch.autograd import Variable

import torch.nn as nn
import torch.nn.functional as F
import torch

os.makedirs("images", exist_ok=True)

parser = argparse.ArgumentParser()
parser.add_argument("--n_epochs", type=int, default=100, help="number of epochs of training")
parser.add_argument("--batch_size", type=int, default=128, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--n_cpu", type=int, default=8, help="number of cpu threads to use during batch generation")
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=400, help="interval betwen image samples")
opt = parser.parse_args()
print(opt)

img_shape = (opt.channels, opt.img_size, opt.img_size)

cuda = True if torch.cuda.is_available() else False


# 生成器
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            '''
            创建一个块,包含多个层
            :param in_feat: 输入数据的个数
            :param out_feat: 输出数据的个数
            :param normalize: 是否正则化
            :return:
            '''
            # 创建线性层
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                # 正则化
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            # ReLU激活函数
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            # 运行文件时传入的参数,default=100,是整个模型的初始输入大小
            *block(opt.latent_dim, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            # 1024 -> (1*28*28)
            nn.Linear(1024, int(np.prod(img_shape))),
            # 双曲正切 激活函数 将元素调整到区间(-1,1)内
            nn.Tanh()
        )

    def forward(self, z):
        # z (128,100)
        # 生成
        # img shape (128,1,28,28)
        img = self.model(z)
        # reshape
        img = img.view(img.size(0), *img_shape)  # img_shape (1,28,28)
        return img  # (128,1,28,28)


# 判别器
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        self.model = nn.Sequential(
            # 用来计算所有元素的乘积,对于有多个维度的数组可以指定轴,如axis=1指定计算每一行的乘积
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1),
            # 输出0~1中的一个值
            nn.Sigmoid(),
        )

    def forward(self, img):
        # img shape -> (128,1,28,28)
        # 调整tensor的形状
        # view中一个参数定为-1,代表自动调整这个维度上的元素个数,以保证元素的总数不变
        img_flat = img.view(img.size(0), -1)  #
        validity = self.model(img_flat)

        return validity


# Loss function
adversarial_loss = torch.nn.BCELoss()

# Initialize generator and discriminator
generator = Generator()
discriminator = Discriminator()

if cuda:
    generator.cuda()
    discriminator.cuda()
    adversarial_loss.cuda()

# Configure data loader
os.makedirs("./data/mnist", exist_ok=True)
dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "./data/mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]
        ),
    ),
    batch_size=opt.batch_size,
    shuffle=True,
)

# Optimizers
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))

Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor

# ----------
#  Training
# ----------

for epoch in range(opt.n_epochs):
    for i, (imgs, _) in enumerate(dataloader):

        # Adversarial ground truths
        # (128,1)
        valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False)  # 一堆1
        fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)  # 一堆0

        # Configure input
        # 真实数据 (128,1,28,28)
        real_imgs = Variable(imgs.type(Tensor))

        # -----------------
        #  Train Generator
        # -----------------

        optimizer_G.zero_grad()

        # Sample noise as generator input
        # 随机初始化一个batch的向量
        # z (128,100)
        z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

        # Generate a batch of images
        # 生成 (128,1,28,28)
        gen_imgs = generator(z)

        # Loss measures generator's ability to fool the discriminator
        # 判别器来判断,产生
        dout = discriminator(gen_imgs)  # (128,1)
        g_loss = adversarial_loss(dout, valid)

        g_loss.backward()
        optimizer_G.step()

        # ---------------------
        #  Train Discriminator
        # ---------------------

        optimizer_D.zero_grad()

        # Measure discriminator's ability to classify real from generated samples
        # 传入真实数据,判别器应该判为1
        # 参数1,判别器输出;参数2:预期结果
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        # 传入虚假(生成的)数据,判别器应该判为0
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2

        d_loss.backward()
        optimizer_D.step()

        print(
            "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
            % (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())
        )

        batches_done = epoch * len(dataloader) + i
        if batches_done % opt.sample_interval == 0:
            save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

05 生成与判别网络定义_ev

判别器需要将实际的数据判为 1,生成器生成的数据判为 0,而生成器希望自己的数据判为 1

03 基于 CycleGan 开源项目实战图像合成

18 强化学习实战系列

19 Openai 顶级黑科技算法及其项目实战

20 面向医学领域的深度学习实战

21 深度学习模型部署与剪枝优化实战

22 自然语言处理必备神器 Huggingface 系列实战

23 自然语言处理通用框架-BERT 实战

24 自然语言处理经典案例实战

01 NLP 常用工具包实战

01 Python 字符串处理_ev

25 知识图谱实战系列

01 知识图谱介绍及其应用领域分析

01 1-知识图谱通俗解读_ev

02 2-知识图谱在搜索引擎中的应用_ev

03 3-知识图谱在医疗领域应用实例_ev

04 4-金融与推荐领域的应用_ev

05 5-数据获取分析_ev

02 知识图谱涉及技术点分析

01 数据关系抽取分析_ev

02 常用 NLP 技术点分析_ev

03 graph-embedding 的作用与效果_ev

04 金融领域图编码实例_ev

05 视觉领域图编码实例_ev

06 图谱知识融合与总结分析_ev

03 Neo4j 数据库实战

01 Neo4j 图数据库介绍_ev

02 Neo4j 数据库安装流程演示_ev

03 可视化例子演示_ev

04_使用 python 操作 neo4j 实例

01 使用 Py2neo 建立连接_ev

# 高于5版本
pip install py2neo==5.0b1

26 语音识别实战系列

27 推荐系统实战系列

01 推荐系统介绍及其应用

01 1-推荐系统通俗解读_ev

02 2-推荐系统发展简介_ev

03 3-应用领域与多方位评估指标_ev

图模型

04 4-任务流程与挑战概述_ev

05 5-常用技术点分析_ev

06 6-与深度学习的结合_ev

02 协同过滤与矩阵分解

01 1-协同过滤与矩阵分解简介_ev

02 2-基于用户与商品的协同过滤_ev

03 3-相似度计算与推荐实例_ev

04 4-矩阵分解的目的与效果_ev

05 5-矩阵分解中的隐向量_ev


06 6-目标函数简介_ev

07 7-隐式情况分析_ev

08 8-Embedding 的作用_ev

03 音乐推荐系统实战

01 1-音乐推荐任务概述_ev

#!/usr/bin/env python
# coding: utf-8

# # 推荐系统
# - 音乐数据处理
# - 基于商品相似性的推荐
# - 基于SVD矩阵分解的推荐

# In[9]:


import pandas as pd
import numpy as np
import time
import sqlite3

data_home = './'

# # 数据读取
# 在数据中只需要 用户,歌曲,播放量

# In[80]:


# triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
#                               sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
#                               names=['user', 'song', 'play_count'])
triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
                              sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
                              names=['user', 'song', 'play_count'],
                              # 分块
                              chunksize=100000).get_chunk(100000)  # 获取100000个

# In[81]:


triplet_dataset.shape

# In[82]:


triplet_dataset.info()

# In[83]:


triplet_dataset.head(n=10)

# # 对每一个用户, 分别统计他的播放总量

# In[84]:


output_dict = {}
with open(data_home + 'train_triplets.txt') as f:
    for line_number, line in enumerate(f):
        user = line.split('\t')[0]
        play_count = int(line.split('\t')[2])
        if user in output_dict:
            # 播放总量 ++
            play_count += output_dict[user]
            output_dict.update({user: play_count})
        # else:
        # 新出现的就直接加入
        output_dict.update({user: play_count})
# 遍历output_dict,生成list[k,v]
output_list = [{'user': k, 'play_count': v} for k, v in output_dict.items()]
play_count_df = pd.DataFrame(output_list)
play_count_df = play_count_df.sort_values(by='play_count', ascending=False)

# In[85]:


play_count_df.to_csv(path_or_buf='user_playcount_df.csv', index=False)

# # 对于每一首歌, 分别统计它的播放总量

# In[86]:


output_dict = {}
with open(data_home + 'train_triplets.txt') as f:
    for line_number, line in enumerate(f):
        song = line.split('\t')[1]
        play_count = int(line.split('\t')[2])
        if song in output_dict:
            # 播放总量 ++
            play_count += output_dict[song]
            output_dict.update({song: play_count})
        # else:
        # 新出现的就直接加入
        output_dict.update({song: play_count})
# 遍历output_dict,生成list[k,v]
output_list = [{'song': k, 'play_count': v} for k, v in output_dict.items()]
song_count_df = pd.DataFrame(output_list)
song_count_df = song_count_df.sort_values(by='play_count', ascending=False)

# In[87]:


song_count_df.to_csv(path_or_buf='song_playcount_df.csv', index=False)

# # 看看目前的排行情况

# In[88]:


play_count_df = pd.read_csv(filepath_or_buffer='user_playcount_df.csv')
play_count_df.head(n=10)

# # 取其中一部分数(按大小排序好的, 是比较重要的数据), 作为实验数据

# In[89]:


# 歌曲总播放数
total_play_count = sum(song_count_df.play_count)
# 前n个播放量的用户的播放量sum 与 歌曲总播放数 的 比值
print((float(play_count_df.head(n=100000).play_count.sum()) / total_play_count) * 100)
play_count_subset = play_count_df.head(n=100000)

# In[90]:


# 播放量排前n的音乐的播放量sum 与 歌曲总播放数 的 比值
(float(song_count_df.head(n=30000).play_count.sum()) / total_play_count) * 100

# In[91]:


song_count_subset = song_count_df.head(n=30000)

# 取10W个用户, 3W首歌

# In[92]:


user_subset = list(play_count_subset.user)
song_subset = list(song_count_subset.song)

# 过滤掉其他用户数据

# In[93]:


triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
                              sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
                              names=['user', 'song', 'play_count'],
                              # 分块
                              chunksize=100000).get_chunk(100000)  # 获取100000个
triplet_dataset_sub = triplet_dataset[triplet_dataset.user.isin(user_subset)]
del (triplet_dataset)
triplet_dataset_sub_song = triplet_dataset_sub[triplet_dataset_sub.song.isin(song_subset)]
del (triplet_dataset_sub)

# In[94]:


triplet_dataset_sub_song.to_csv(path_or_buf=data_home + 'triplet_dataset_sub_song.csv', index=False)

# 我们的数据量

# In[95]:


triplet_dataset_sub_song.shape

# In[96]:


triplet_dataset_sub_song.head(n=10)

02 2-数据集整合_ev

#!/usr/bin/env python
# coding: utf-8

# # 推荐系统
# - 音乐数据处理
# - 基于商品相似性的推荐
# - 基于SVD矩阵分解的推荐

# In[145]:


import pandas as pd
import numpy as np
import time
import sqlite3

data_home = './'

# # 数据读取
# 在数据中只需要 用户,歌曲,播放量

# In[146]:


triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
                              sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
                              names=['user', 'song', 'play_count'])
# triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
#                               sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
#                               names=['user', 'song', 'play_count'],
#                               # 分块
#                               chunksize=100000).get_chunk(100000)  # 获取100000个


# In[147]:


triplet_dataset.shape

# In[148]:


triplet_dataset.info()

# In[149]:


triplet_dataset.head(n=10)

# # 对每一个用户, 分别统计他的播放总量

# In[ ]:


output_dict = {}
with open(data_home + 'train_triplets.txt') as f:
    for line_number, line in enumerate(f):
        user = line.split('\t')[0]
        play_count = int(line.split('\t')[2])
        if user in output_dict:
            # 播放总量 ++
            play_count += output_dict[user]
            output_dict.update({user: play_count})
        # else:
        # 新出现的就直接加入
        output_dict.update({user: play_count})
# 遍历output_dict,生成list[k,v]
output_list = [{'user': k, 'play_count': v} for k, v in output_dict.items()]
play_count_df = pd.DataFrame(output_list)
play_count_df = play_count_df.sort_values(by='play_count', ascending=False)

# In[ ]:


play_count_df.to_csv(path_or_buf='user_playcount_df.csv', index=False)

# # 对于每一首歌, 分别统计它的播放总量

# In[ ]:


output_dict = {}
with open(data_home + 'train_triplets.txt') as f:
    for line_number, line in enumerate(f):
        song = line.split('\t')[1]
        play_count = int(line.split('\t')[2])
        if song in output_dict:
            # 播放总量 ++
            play_count += output_dict[song]
            output_dict.update({song: play_count})
        # else:
        # 新出现的就直接加入
        output_dict.update({song: play_count})
# 遍历output_dict,生成list[k,v]
output_list = [{'song': k, 'play_count': v} for k, v in output_dict.items()]
song_count_df = pd.DataFrame(output_list)
song_count_df = song_count_df.sort_values(by='play_count', ascending=False)

# In[ ]:


song_count_df.to_csv(path_or_buf='song_playcount_df.csv', index=False)

# # 看看目前的排行情况

# In[ ]:


play_count_df = pd.read_csv(filepath_or_buffer='user_playcount_df.csv')
play_count_df.head(n=10)

# # 取其中一部分数(按大小排序好的, 是比较重要的数据), 作为实验数据

# In[ ]:


# 歌曲总播放数
total_play_count = sum(song_count_df.play_count)
# 前n个播放量的用户的播放量sum 与 歌曲总播放数 的 比值
print((float(play_count_df.head(n=100000).play_count.sum()) / total_play_count) * 100)
play_count_subset = play_count_df.head(n=100000)

# In[ ]:


# 播放量排前n的音乐的播放量sum 与 歌曲总播放数 的 比值
(float(song_count_df.head(n=30000).play_count.sum()) / total_play_count) * 100

# In[ ]:


song_count_subset = song_count_df.head(n=30000)

# 取10W个用户, 3W首歌

# In[ ]:


user_subset = list(play_count_subset.user)
song_subset = list(song_count_subset.song)

# 过滤掉其他用户数据

# In[ ]:


triplet_dataset = pd.read_csv(filepath_or_buffer=data_home + 'train_triplets.txt',
                              sep='\t', header=None,  # 分隔符 \t, 第一行不是属性名称
                              names=['user', 'song', 'play_count'],
                              # 分块
                              chunksize=100000).get_chunk(100000)  # 获取100000个
triplet_dataset_sub = triplet_dataset[triplet_dataset.user.isin(user_subset)]
del (triplet_dataset)
triplet_dataset_sub_song = triplet_dataset_sub[triplet_dataset_sub.song.isin(song_subset)]
del (triplet_dataset_sub)

# In[ ]:


triplet_dataset_sub_song.to_csv(path_or_buf=data_home + 'triplet_dataset_sub_song.csv', index=False)

# 我们的数据量

# In[ ]:


triplet_dataset_sub_song.shape

# In[ ]:


triplet_dataset_sub_song.head(n=10)

# # 加入音乐详细信息
#
# .db文件需要稍微处理下 转换成csv

# In[ ]:


conn = sqlite3.connect(data_home + 'track_metadata.db')
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
cur.fetchall()

# In[ ]:


track_metadata_df = pd.read_sql(con=conn, sql='select * from songs')
# 找到过滤出来的歌曲 对应的 数据库信息
track_metadata_df_sub = track_metadata_df[track_metadata_df.song_id.isin(song_subset)]

# In[ ]:


track_metadata_df_sub.to_csv(path_or_buf=data_home + 'track_metadata_df_sub.csv', index=False)

# In[ ]:


track_metadata_df_sub.shape

# # 我们现有的数据

# In[ ]:


triplet_dataset_sub_song.head()

# In[ ]:


track_metadata_df_sub.head()

# # 清洗数据集
#
# 去除掉无用的和重复的

# In[ ]:


del (track_metadata_df_sub['track_id'])
del (track_metadata_df_sub['artist_mbid'])
track_metadata_df_sub = track_metadata_df_sub.drop_duplicates(['song_id'])
# 拼接 triplet_dataset_sub_song + track_metadata_df_sub
# track_metadata_df_sub的song 关联 track_metadata_df_sub的song_id
triplet_dataset_sub_song_merged = pd.merge(triplet_dataset_sub_song, track_metadata_df_sub,
                                           how='left', left_on='song', right_on='song_id')
# play_count 重命名为 listen_count
triplet_dataset_sub_song_merged.rename(columns={'play_count': 'listen_count'}, inplace=True)

# In[ ]:


# 删除无用字段
del (triplet_dataset_sub_song_merged['song_id'])
del (triplet_dataset_sub_song_merged['artist_id'])
del (triplet_dataset_sub_song_merged['duration'])
del (triplet_dataset_sub_song_merged['artist_familiarity'])
del (triplet_dataset_sub_song_merged['artist_hotttnesss'])
del (triplet_dataset_sub_song_merged['track_7digitalid'])
del (triplet_dataset_sub_song_merged['shs_perf'])
del (triplet_dataset_sub_song_merged['shs_work'])

# 搞定数据

# In[ ]:


triplet_dataset_sub_song_merged.head(n=10)

# # 看看音乐数据集的情况
#
# ## 最流行的歌曲

# In[ ]:


popular_songs = triplet_dataset_sub_song_merged[['title', 'listen_count']]
.groupby('title').sum().reset_index()
popular_songs_top_20 = popular_songs.sort_values('listen_count', ascending=False).head(n=20)
import matplotlib.pyplot as plt;

plt.rcdefaults()
import numpy as np
import matplotlib.pyplot as plt

objects = (list(popular_songs_top_20['title']))
y_pos = np.arange(len(objects))
performance = list(popular_songs_top_20['listen_count'])

plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular songs')

plt.show()

# # 最受欢迎的release

# In[ ]:


popular_release = triplet_dataset_sub_song_merged[['release', 'listen_count']]
.groupby('release').sum().reset_index()
popular_release_top_20 = popular_release.sort_values('listen_count', ascending=False).head(n=20)

objects = (list(popular_release_top_20['release']))
y_pos = np.arange(len(objects))
performance = list(popular_release_top_20['listen_count'])

plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular Release')

plt.show()

# # 最受欢迎的歌手

# In[ ]:


popular_artist = triplet_dataset_sub_song_merged[['artist_name', 'listen_count']]
.groupby('artist_name').sum().reset_index()
popular_artist_top_20 = popular_artist.sort_values('listen_count', ascending=False).head(n=20)

objects = (list(popular_artist_top_20['artist_name']))
y_pos = np.arange(len(objects))
performance = list(popular_artist_top_20['listen_count'])

plt.bar(y_pos, performance, align='center', alpha=0.5)
plt.xticks(y_pos, objects, rotation='vertical')
plt.ylabel('Item count')
plt.title('Most popular Artist')

plt.show()

# # 用户播放量分布

# In[ ]:


user_song_count_distribution = triplet_dataset_sub_song_merged[['user', 'title']]
.groupby('user').count().reset_index().sort_values(by='title', ascending=False)
user_song_count_distribution.title.describe()

# In[ ]:


x = user_song_count_distribution.title
n, bins, patches = plt.hist(x, 50, facecolor='green', alpha=0.75)
plt.xlabel('Play Counts')
plt.ylabel('Num of Users')
plt.title(r'$\mathrm{Histogram\ of\ User\ Play\ Count\ Distribution}\ $')
plt.grid(True)
plt.show()

03 3-基于物品的协同过滤_ev

04 知识图谱与 Neo4j 数据库实例

28 AI 课程所需安装软件教程

29 额外补充


 上一篇
【2023.05】XN-AI人工智能工程师-NLP必备技能-完结-无课件(15.43G) 【2023.05】XN-AI人工智能工程师-NLP必备技能-完结-无课件(15.43G)
01-自然语言处理基础知识与操作第一章自然语言处理基础概述 文本数据、字、词、term【www.itzyku.com】 字符串处理【www.itzyku.com】 # 1. 清理与替换 en_str = ' hello world, hel
2023-08-21
下一篇 
Complete Machine Learning & Data Science Bootcamp 2022 (11.2021) Complete Machine Learning & Data Science Bootcamp 2022 (11.2021)
02 - Machine Learning 10102.01 What Is Machine Learning_ 如果要去小明家,我们可以使用数学算法来计算距离,找出最优解 但是如何让机器识别小猫?或者进行其他复杂的工作?如何描述小猫
2023-08-21
  目录