[深度学习实战]基于PyTorch的深度学习实战(下)[Mnist手写数字图像识别]

目录

  • 一、前言
  • 二、Mnist手写数字图像识别
    • 2.1 加载数据
      • 2.1.1 下载地址
      • 2.1.2 用 numpy 读取 mnist.npz
    • 2.2 定义卷积模型
    • 2.3 开始训练
    • 2.4 完整代码
    • 2.5 验证结果
    • 2.6 修改参数
  • 三、后记


PyTorch——开源的Python机器学习库

一、前言

  首先感谢所有点开本文的朋友们!基于PyTorch的深度学习实战可能要告一段落了。本想着再写几篇关于PyTorch神经网络深度学习的文章来着,可无奈项目时间紧任务重,要求短时间内出图并做好参数拟合。所以只得转战Matlab编程,框架旧就旧吧,无所谓了 (能出结果就行)
  往期回顾:
[深度学习实战]基于PyTorch的深度学习实战(上)[变量、求导、损失函数、优化器]

[深度学习实战]基于PyTorch的深度学习实战(中)[线性回归、numpy矩阵的保存、模型的保存和导入、卷积层、池化层]


二、Mnist手写数字图像识别

  一个典型的神经网络的训练过程大致分为以下几个步骤:
  (1)首先定义神经网络的结构,并且定义各层的权重参数的规模和初始值
  (2)然后将输入数据分成多个批次输入神经网络
  (3)将输入数据通过整个网络进行计算
  (4)每次迭代根据计算结果和真实结果的差值计算损失
  (5)根据损失对权重参数进行反向求导传播
  (6)更新权重值更新过程使用下面的公式
  weight = weight + learning_rate * gradient。其中 weight 是上一次的权重值,learning_rate 是学习步长,gradient 是求导值
  下面我们还是以经典的图像识别卷积网络作为例子来学习 pytorch 的用法。选择一个较小的数据集进行训练,这里我们选择 mnist 手写识别数据集,该数据集是 0-9 个数字的训练数据和测试数据,训练数据 60000 张图片,测试数据 10000 张图片。每张图片是 28×28 像素大小的单通道黑白图片,这样读到内存数据组就是 28 * 28 * 1 大小。这里的 28 * 28 是宽度和高度的像素数量,1 是图像通道数据(如果是彩色图像就是 3 道路,内存数据为 28 * 28 * 3 大小)。
  我们先看看样本图像的模样:
在这里插入图片描述
  好,下一步我们构建一个简单的卷积神经网络模型这个模型的输入是图片,输出是 0-9 的数字,这是一个典型的监督式分类神经网络

2.1 加载数据

  这里我们不打算下载原始的图像文件然后通过 opencv 等图像库读取到数组而是直接下载中间数据。当然读者也可以下载原始图像文件从头开始装载数据,这样对整个模型会有更深刻的体会。
  我们用 mnist 数据集作例子,下载方法有两种:

2.1.1 下载地址

  http://yann.lecun.com/exdb/mnist/。一共四个文件:
  train-images-idx3-ubyte.gz: training set images (9912422 bytes)
  train-labels-idx1-ubyte.gz: training set labels (28881 bytes)
  t10k-images-idx3-ubyte.gz: test set images (1648877 bytes)
  t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)
  这些文件中是已经处理过的数组数据,通过numpy的相关方法读取到训练数据集和测试数据集数组中注意调用 load_mnist 方法之前先解压上述四个文件

from __future__ import print_function
import os
import struct
import numpy as np
def load_mnist(path, kind='train'):
labels_path = os.path.join(path, '%s-labels.idx1-ubyte' % kind)
images_path = os.path.join(path, '%s-images.idx3-ubyte' % kind)
with open(labels_path, 'rb') as lbpath:
magic, n = struct.unpack('>II', lbpath.read(8))
labels = np.fromfile(lbpath, dtype=np.uint8)
with open(images_path, 'rb') as imgpath:
magic, num, rows, cols = struct.unpack(">IIII", imgpath.read(16))
images = np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784)
return images, labels
X_train, y_train = load_mnist('./data', kind='train')
print('Rows: %d, columns: %d' % (X_train.shape[0], X_train.shape[1]))
X_test, y_test = load_mnist('./data', kind='t10k')
print('Rows: %d, columns: %d' % (X_test.shape[0], X_test.shape[1]))

  显示结果

D:\AI>python mnist_cnn.py
Rows: 60000, columns: 784
Rows: 10000, columns: 784

  我们看到训练数据的行数是 60000,表示 60000 张图片,列是 784,表示 28281=784 个像素,测试数据是 10000 张图片,在后面构建卷积模型时,要先将 784 个像素的列 reshape 成 28281 维度
  例如:

image1 = X_train[1]
image1 = image1.astype('float32')
image1 = image1.reshape(28,28,1)

  我们还可以将这些图像数组导出为 jpg 文件,比如下面的代码:

import cv2
cv2.imwrite('1.jpg',image1)

  当前目录下的 1.jpg 文件都是我们导出的图像文件了。
  图像的显示:

import cv2
import numpy as np
img = cv2.imread("C:\lena.jpg")
cv2.imshow("lena",img)
cv2.waitKey(10000)

2.1.2 用 numpy 读取 mnist.npz

  可以直接从亚马逊下载文件:
  https://s3.amazonaws.com/imgdatasets/mnist.npz
  Mnist.npz 是一个 numpy 数组文件。如果下载的是 mnist.npz.gz 文件,则用 gunzip mnist.npz.gz 先解压成 mnist.npz,然后再处理也可以调用 keras 的方法来下载

from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()

  通过 mnist.load_data() 下载的 mnist.npz 会放在当前用户的 .keras 目录中。路径名称:
  ~/.keras/datasets/mnist.npz
  然后调用 numpy 来加载 mnist.npz。
  示例代码:

import numpy as np
class mnist_data(object):
def load_npz(self,path):
f = np.load(path)
for i in f:
print i
x_train = f['trainInps']
y_train = f['trainTargs']
x_test = f['testInps']
y_test = f['testTargs']
f.close()
return (x_train, y_train), (x_test, y_test)
a = mnist_data()
(x_train, y_train), (x_test, y_test) = a.load_npz('D:/AI/torch/data/mnist.npz')
print ("train rows:%d,test rows:%d"% (x_train.shape[0], x_test.shape[0]))
print("x_train shape",x_train.shape)
print("y_train shape",y_train.shape )

  结果:
在这里插入图片描述
  一共 60000 张训练图片,10000 张测试图片。训练图像的大小 784 个像素,也就是 1 通道的 28*28 的手写图片。标签是长度为 10 的 (0,1) 向量,表示 0-9 是个数字。例如数字 1 就表示为 [0,1,0,0,0,0,0,0,0,0]。
  让我们打印出第一张图片的截图和标签。代码如下:

tt=x_train[1]
tt=tt.astype('float32')
image = tt.reshape(28,28,1)
cv2.imwrite("001.png",image)
print("tt label:",y_train[1])

在这里插入图片描述
在这里插入图片描述
  标签为 [0,0,0,1,0,0,0,0,0,0]。


2.2 定义卷积模型

  这一步我们定义自己的卷积模型,对于 28 * 28 的数组,我们定义 (5,5) 的卷积核大小比较合适,卷积核的大小可根据图像大小灵活设置,比如 (3,3),(5,5),(9,9)等。一般卷积核的大小是奇数
  输入数据首先连接 1 个卷积层加 1 个池化层,conv1 和 pool1,假设我们定义 conv1 的神经元数目为 10,卷积核大小 (5,5),定义 pool1 的大小 (2,2),意思将 2 * 2区域共 4 个像素统计为 1 个像素这样这层数据量减少 4 倍。这时候输出图像大小为 (28-5+1)/2=12。输出数据维度(10,12,12)。
  接着再来一次卷积池化,连接 1 个卷积层加 1 个池化层,conv2 和 pool2,假设我们定义 conv2 的神经元数目为 20,卷积核大小 (5,5),定义pool2的大小 (2,2),意思将 2*2 区域共 4 个像素统计为 1 个像素,这样这层数据量减少 4 倍。这时候输出图像大小为 (12-5+1)/2=4。输出数据维度 (20,4,4)。
  然后接一个 dropout 层,设置 dropout=0.2,随机抛弃部分数据
  最后连续接两个全连接层,第一个全连接层 dense1 输入维度 20 * 4 * 4,输出 320,第二个全连接层 dense1 输入维度 60,输出 10。
  模型定义的 pytorch 代码如下

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 60)
self.fc2 = nn.Linear(60, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x)
model = Net()
print(model)

  注解:nn.Conv2d(1, 10, kernel_size=5) 是指将 1 通道的图像数据的输入卷积成 10 个神经元这 1 个通道跟 10 个神经元都建立连接,然后神经网络对每个连接计算出不同的权重值,某个神经元上的权重值对原图卷积来提取该神经元负责的特征这个过程是神经网络自动计算得出的
  那么 nn.Conv2d(10, 20, kernel_size=5) 是指将 10 通道的图像数据的输入卷积成 20 个神经元,同样的,这 10 个通道会和 20 个神经元的每一个建立连接,那么10 个通道如何卷积到 1 个通道呢?一般是取 10 个通道的平均值作为最后的结果
  执行该脚本,将卷积模型的结构打印出来

[root@iZ25ix41uc3Z ai]# python mnist_torch.py
Net (
(conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
(conv2_drop): Dropout2d (p=0.5)
(fc1): Linear (320 -> 50)
(fc2): Linear (50 -> 10)
)

2.3 开始训练

  数据准备好模型建立好,下面根据神经网络的三部曲,就是选择损失函数和梯度算法对模型进行训练了
  损失函数通俗讲就是计算模型的计算结果和真实结果之间差异性的函数典型的如距离的平方和再开平方,对于图像分类来说,我们取的损失函数是:

F.log_softmax(x)

  在神经网络的训练过程中,我们使用 loss.backward() 来反向传递修正反向修正就是根据计算和真实结果的差值(就是损失)来反向逆传播修正各层的权重参数,修正之后的结果保存在 .grad 中,因此每轮迭代执行 loss.backward() 的时候要先对 .grad 清零。

model.zero_grad()
print('conv1.bias.grad before backward')
print(model.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(model.conv1.bias.grad)

  优化算法一般是指迭代更新权重参数的梯度算法,这里我们选择随机梯度算法 SGD
  SGD 的算法如下:

weight = weight - learning_rate * gradient

  可以用一段简单的 python 脚本模拟这个 SGD 的过程

learning_rate = 0.01
for f in model.parameters():
f.data.sub_(f.grad.data * learning_rate)

  pytorch 中含有其他的优化算法,如 Nesterov-SGD, Adam, RMSProp 等。
  它们的用法和 SGD 基本类似,这里就不一一介绍了。
  训练代码如下:

import torch.optim as optim
input = Variable(torch.randn(1, 1, 32, 32))
out = model(input)
print(out)
# create your optimizer
optimizer = optim.SGD(model.parameters(), lr=0.01)
# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = model(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update

2.4 完整代码

  最后的代码如下所示,这里我们做了一点修改,每次迭代完会将模型参数保存到文件,下次再次执行脚本会自动加载上次迭代后的数据。整个完整的代码如下:

'''
Trains a simple convnet on the MNIST dataset.
'''
from __future__ import print_function
import os
import struct
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
def load_mnist(path, kind='train'):
"""Load MNIST data from `path`"""
labels_path = os.path.join(path, '%s-labels.idx1-ubyte' % kind)
images_path = os.path.join(path, '%s-images.idx3-ubyte' % kind)
with open(labels_path, 'rb') as lbpath:
magic, n = struct.unpack('>II', lbpath.read(8))
labels = np.fromfile(lbpath, dtype=np.uint8)
with open(images_path, 'rb') as imgpath:
magic, num, rows, cols = struct.unpack(">IIII", imgpath.read(16))
images = np.fromfile(imgpath, dtype=np.uint8).reshape(len(labels), 784)
return images, labels
X_train, y_train = load_mnist('./data', kind='train')
print("shape:",X_train.shape)
print('Rows: %d, columns: %d' % (X_train.shape[0], X_train.shape[1]))
X_test, y_test = load_mnist('./data', kind='t10k')
print('Rows: %d, columns: %d' % (X_test.shape[0], X_test.shape[1]))
batch_size = 100
num_classes = 10
epochs = 2
# input image dimensions
img_rows, img_cols = 28, 28
x_train= X_train
x_test=X_test
if 'channels_first' == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
num_samples=x_train.shape[0]
print("num_samples:",num_samples)
'''
build torch model
'''
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x)
model = Net()
if os.path.exists('mnist_torch.pkl'):
model = torch.load('mnist_torch.pkl')
print(model)
'''
trainning
'''
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
#loss=torch.nn.CrossEntropyLoss(size_average=True)
def train(epoch,x_train,y_train):
num_batchs = num_samples/ batch_size
model.train()
for k in range(num_batchs):
start,end = k*batch_size,(k+1)*batch_size
data, target = Variable(x_train[start:end],requires_grad=False), Variable(y_train[optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if k % 10 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, k * len(data), num_samples,
100. * k / num_samples, loss.data[0]))
torch.save(model, 'mnist_torch.pkl')
'''
evaludate
'''
def test(epoch):
model.eval()
test_loss = 0
correct = 0
if 2>1:
data, target = Variable(x_test, volatile=True), Variable(y_test)
output = model(data)
test_loss += F.nll_loss(output, target).data[0]
pred = output.data.max(1)[1] # get the index of the max log-probability
correct += pred.eq(target.data).cpu().sum()
test_loss = test_loss
test_loss /= len(x_test) # loss function already averages over batch size
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(x_test),
100. * correct / len(x_test)))
x_train=torch.from_numpy(x_train).float()
x_test=torch.from_numpy(x_test).float()
y_train=torch.from_numpy(y_train).long()
y_test=torch.from_numpy(y_test).long()
for epoch in range(1,epochs):
train(epoch,x_train,y_train)
test(epoch)

2.5 验证结果

  2 次迭代后:Test set: Average loss: 0.3419, Accuracy: 9140/10000 (91%)
  3 次迭代后:Test set: Average loss: 0.2362, Accuracy: 9379/10000 (94%)
  4 次迭代后:Test set: Average loss: 0.2210, Accuracy: 9460/10000 (95%)
  5 次迭代后:Test set: Average loss: 0.1789, Accuracy: 9532/10000 (95%)
  顺便说一句,pytorch 的速度比 keras 确实要快很多,每次迭代几乎 1 分钟内就完成了,确实很不错!


2.6 修改参数

  我们把卷积层的神经元个数重新设置一下,第一层卷积加到 32 个,第二层卷积神经元加到 64 个。则新的模型为下面的组织:

{
(conv1): Conv2d(1, 32, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1))
(conv2_drop): Dropout2d (p=0.5)
(fc1): Linear (1024 -> 100)
(fc2): Linear (100 -> 10)
)

  然后同样的步骤我们跑一遍看看结果如何,这次明显慢了很多,但看的出来准确度也提高了一些:
  第 1 次迭代:Test set: Average loss: 0.3159, Accuracy: 9038/10000 (90%)
  第 2 次迭代:Test set: Average loss: 0.1742, Accuracy: 9456/10000 (95%)
  第 3 次迭代:Test set: Average loss: 0.1234, Accuracy: 9608/10000 (96%)
  第 4 次迭代:Test set: Average loss: 0.1009, Accuracy: 9694/10000 (97%)


三、后记

  至此,我们已经完成了机器学习中的深度学习的大部分实用知识点。其实,我这里都是演示已经跑通的程序,而在修改程序配置环境等等这些“绕弯路”的经历才更能锻炼人。基于PyTorch的深度学习实战还会有个补充篇放出,补充RNN和LSTM原理相关,作为《基于PyTorch的深度学习实战》这个方向的最后一篇。稍后见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/49698.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux:shell命令运行原理和权限的概念

文章目录 shell和kernelshell的概念和原理Linux的权限文件的权限文件的类型文件的权限管理权限的实战应用 shell和kernel 从狭义上来讲,Linux是一个操作系统,我们叫它叫kernel,意思是核心,核心的意思顾名思义,就是最关…

微信小程序——同一控件的点击与长按事件共存的解决方案

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

GRACE数据反演的新理解

一、问题提出 “重力恢复与气候实验”(GRACE)为监测地球系统内全球大尺度质量变化提供了一种新途径。自2002年3月发射以来,GRACE一直在生成时间变化的重力场模型,这些模型可用于量化与全球气候变化相关的地球系统不同组成部分内的…

C++day7(异常处理机制、Lambda表达式、类型转换、STL标准库模板、迭代器、list)

#include <iostream>using namespace std; template <typename T> class vector { private:T* first;T* last;T* end; public:vector():first(new T),last(first),end(first){cout<<"无参构造"<<endl;}//无参构造vector(T* f):first(f),last…

24考研数据结构-线性表4

目录 2.4.4单链表的查找操作&#xff08;默认带头节点&#xff0c;不带头节点后续更新&#xff09;2.4.4.1 按位查找操作2.4.4.2 按值查找操作2.4.4.3 求单链表的长度&#xff08;带和不带头节点都写了&#xff09;2.4.4.4 知识回顾与重要考点 2.4.5 单链表的创建操作2.4.5.1 头…

STL中的神秘“指针”:迭代器

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;C学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大…

kaggle新赛:Bengali.AI 语音识别大赛赛题解析

赛题名称&#xff1a;Bengali.AI Speech Recognition 赛题链接&#xff1a;https://www.kaggle.com/competitions/bengaliai-speech 赛题背景 竞赛主办方 Bengali.AI 致力于加速孟加拉语&#xff08;当地称为孟加拉语&#xff09;的语言技术研究。Bengali.AI 通过社区驱动的…

uni-app如何生成正式的APK

第一步&#xff1a; 进入dcloud官网https://dcloud.io/&#xff0c;点击开发者后台进入登录注册页面 第二步&#xff1a;登录之后跳到项目列表&#xff0c;选择自己想要打包的项目 点击进去如果没有生成证书&#xff0c;点击生成证书&#xff0c;如果显示证书已生成就不用管了…

【Windows】WDS中如何跳过语言选择以及身份验证

WDS&#xff08;Windows Deployment Services&#xff09;是微软的一项网络服务&#xff0c;用于快速和方便地部署Windows操作系统到多台计算机上。它提供了一种自动化的方式来安装、配置和管理操作系统映像&#xff0c;使企业能够快速部署和更新大量的计算机系统。网上有很多W…

【Kaggle】Kaggle数据集如何使用命令语句下载?

一、Kaggle数据集如何下载 1.1 问题的起因 最近看到了 Google 组织的 Kaggle 比赛&#xff0c;想自己试一下&#xff0c;但是数据集太大了&#xff0c;将近有370G的数据。直接下载的话&#xff0c;网速太慢&#xff0c;可能要下载3-4天&#xff0c;所以萌生了用命令语句下载的…

详解rocketMq通信模块升级构想

本文从开发者的角度深入解析了基于netty的通信模块, 并通过简易扩展实现微服务化通信工具雏形, 适合于想要了解netty通信框架的使用案例, 想了解中间件通信模块设计, 以及微服务通信底层架构的同学。希望此文能给大家带来通信模块架构灵感。 概述 网络通信是很常见的需求&#…

并发编程可能出现的核心问题

2.1非可见性 如果主内存里有个静态变量flagfalse&#xff0c;然后线程A和B在工作内存都需要操作flag&#xff0c;线程A是while(!false){}&#xff0c;而线程B将flag改为true&#xff0c;但是由于线程A和线程B之间工作内存互相不可见&#xff0c;线程A就会陷入死循环。 2.2指令…

【C++11】——类的新功能

目录 1. 默认成员函数 2. 类成员变量初始化 3. 强制生成默认函数的关键字default 4. 禁止生成默认函数的关键字delect 5. 继承和多态的final与override关键字 6. 测试案例 1. 默认成员函数 原来C类中&#xff08;C11之前&#xff09;&#xff0c;有6个默认成员函数&…

GAMES101 笔记 Lecture12 Geometry3

目录 Mesh Operations: Geometry ProcessingMesh Subdivision (曲面细分)Mesh Simplification(曲面简化)Mesh Regularization(曲面正则化) Subdivision(细分)Loop Subdivision(Loop细分)如何来调整顶点位置呢&#xff1f;Loop Subdivision Result (Loop细分的结果) Catmull-Cla…

大数据-Spark批处理实用广播Broadcast构建一个全局缓存Cache

1、broadcast广播 在Spark中&#xff0c;broadcast是一种优化技术&#xff0c;它可以将一个只读变量缓存到每个节点上&#xff0c;以便在执行任务时使用。这样可以避免在每个任务中重复传输数据。 2、构建缓存 import org.apache.spark.sql.SparkSession import org.apache.s…

【【51单片机11.0592晶振红外遥控】】

51单片机11.0592晶振红外遥控 红外遥控&#xff0c;51单片机完结 这是初步实现的架构 怎么实现内部的详细逻辑 我们用状态机的方法 0状态时一个空闲状态 当它接收到下降沿开始计时然后转为1状态 1状态下 寻找start 或者repeat的信号 再来下降沿读出定时器的值 如果是start 那…

Python爬虫基础知识点有哪些

目录 Python爬虫基础知识点 Requests库 Beautiful Soup库 正则表达式 数据存储 防止被反爬虫策略 爬虫调度和任务管理 认识robots.txt文件 反爬虫法律与道德 示例代码 Requests库 Beautiful Soup库 正则表达式 数据存储 防止被反爬虫策略 结语 网络世界中信息的…

如图,△ABC中,AD是角平分线,E、F分别为AC、AB上的点,且∠AED+∠AFD=180°.试问:DE与DF有何关系,并说明理由.

Question&#xff1a; 如图&#xff0c;△ABC中&#xff0c;AD是角平分线&#xff0c;E、F分别为AC、AB上的点&#xff0c;且∠AED∠AFD180&#xff0e;试问&#xff1a;DE与DF有何关系&#xff0c;并说明理由&#xff0e; Answer&#xff1a; 分析&#xff1a;过D作DM⊥AB于…

为 Google Play 即将推出基于区块链的内容政策做好准备

作者 / Joseph Mills, Group Product Manager, Google Play 作为一个平台&#xff0c;Google Play 一直致力于帮助开发者将创新理念变为现实。Google Play 上托管了许多和区块链相关的应用&#xff0c;我们深知合作伙伴们希望扩展这些应用&#xff0c;并利用 NFT 等代币化数字资…