深度学习:从入门到放弃

`早茶月光 `早茶月光     2022-09-21     564

关键词:

https://zhuanlan.zhihu.com/p/22976342

 

 
技术分享

FCN学习:Semantic Segmentation

技术分享余俊
1 年前

感谢@huangh12 @郑途 @麦田守望者对标签图像生成的研究和讨论,这几天研究了一下,补充如下。

-----------------------------------------------------分割线------------------------------------------------------------

谢谢@潘达的评论,这一篇确实有很多点没有分析完,当时想着后来加上去,但是由于工作的关系,也就不了了之了。最近终于忙完了一个项目,有时间更新一下文章了!

----------------------------------------------------- 分割线------------------------------------------------------------

本来这一篇是想写Faster-RCNN的,但是Faster-RCNN中使用了RPN(Region Proposal Network)替代Selective Search等产生候选区域的方法。RPN是一种全卷积网络,所以为了透彻理解这个网络,首先学习一下FCN(fully convolutional networks)Fully Convolutional Networks for Semantic Segmentation

全卷积网络首现于这篇文章。这篇文章是将CNN结构应用到图像语义分割领域并取得突出结果的开山之作,因而拿到了CVPR 2015年的best paper.

图像语义分割,简而言之就是对一张图片上的所有像素点进行分类

技术分享如上图就是一个语义分割的例子,不同的颜色代表不同的类别

下面简单介绍一下论文,重点介绍文中的那几个结构:

一.论文解读

1.Introduction

CNN这几年一直在驱动着图像识别领域的进步。无论是整张图片的分类(ILSVRC),还是物体检测,关键点检测都在CNN的帮助下得到了非常大的发展。但是图像语义分割不同于以上任务,这是个空间密集型的预测任务,换言之,这需要预测一幅图像中所有像素点的类别。

以往的用于语义分割的CNN,每个像素点用包围其的对象或区域类别进行标注,但是这种方法不管是在速度上还是精度上都有很大的缺陷。

本文提出了全卷积网络(FCN)的概念,针对语义分割训练一个端到端,点对点的网络,达到了state-of-the-art。这是第一次训练端到端的FCN,用于像素级的预测;也是第一次用监督预训练的方法训练FCN。

2.Fully Convolutional networks & Architecture

下面我们重点看一下FCN所用到的三种技术:

1.卷积化(convolutionalization)

分类所使用的网络通常会在最后连接全连接层,它会将原来二维的矩阵(图片)压缩成一维的,从而丢失了空间信息,最后训练输出一个标量,这就是我们的分类标签。

而图像语义分割的输出则需要是个分割图,且不论尺寸大小,但是至少是二维的。所以,我们丢弃全连接层,换上卷积层,而这就是所谓的卷积化了。

技术分享这幅图显示了卷积化的过程,图中显示的是AlexNet的结构,简单来说卷积化就是将其最后三层全连接层全部替换成卷积层

2.上采样(Upsampling)

上采样也就是对应于上图中最后生成heatmap的过程。

在一般的CNN结构中,如AlexNet,VGGNet均是使用池化层来缩小输出图片的size,例如VGG16,五次池化后图片被缩小了32倍;而在ResNet中,某些卷积层也参与到缩小图片size的过程。我们需要得到的是一个与原图像size相同的分割图,因此我们需要对最后一层进行上采样,在caffe中也被称为反卷积(Deconvolution),可能叫做转置卷积(conv_transpose)更为恰当一点。

为理解转置卷积,我们先来看一下Caffe中的卷积操作是怎么进行的

在caffe中计算卷积分为两个步骤:

1)使用im2col操作将图片转换为矩阵

2)调用GEMM计算实际的结果

简单来说就是以下的矩阵相乘操作:

技术分享

下面使用一下@贾扬清大神在知乎上解释Caffe计算卷积的图来解释一下上面的操作:

技术分享技术分享技术分享

这是将输入转换为feature matrix的过程(im2col),这里的feature matrix对应于上图中的矩阵B的转置,k即是卷积核的尺寸,C为输入的维度,矩阵B中的K=C x k x k,当然N就等于H‘ x W‘了,H‘,W‘对应于输出的高和宽,显然H‘=(H-k+2*pad)/stride+1,W‘=(W-k+2*pad)/stride+1,这组公式想必大家很熟悉了,就不加以解释了,接下来我们看看A矩阵,

技术分享A矩阵对应于filter matrix,Cout是输出的维度,亦即卷积核的个数,K= C x k x k.

所以在caffe中,先调用im2col将filters和input转换为对应filter matrix(A)和feature matrix(B‘),然后再用filter matrix乘以feature matrix的转置,就得到了C矩阵,亦即输出矩阵,再将C矩阵通过col2im转换为对应的feature map,这就是caffe中完整的卷积的前向传播过程。

那么当反向传播时又会如何呢?首先我们已经有从更深层的网络中得到的技术分享

根据矩阵微分公式技术分享,可推得
技术分享,

Caffe中的卷积操作简单来说就是这样,那转置卷积呢?
其实转置卷积相对于卷积在神经网络结构的正向和反向传播中做相反的运算。
所以所谓的转置卷积其实就是正向时左乘技术分享,而反向时左乘技术分享,即技术分享的运算。

虽然转置卷积层和卷积层一样,也是可以train参数的,但是实际实验过程中,作者发现,让转置卷积层可学习,并没有带来performance的提升,所以实验中的转置卷积层的lr全部被置零了

注意:在代码中可以看出,为了得到和原图像size一模一样的分割图,FCN中还使用了crop_layer来配合deconvolution层

3.跳跃结构(Skip Architecture)

其实直接使用前两种结构就已经可以得到结果了,但是直接将全卷积后的结果上采样后得到的结果通常是很粗糙的。所以这一结构主要是用来优化最终结果的,思路就是将不同池化层的结果进行上采样,然后结合这些结果来优化输出,具体结构如下:

技术分享而不同的结构产生的结果对比如下:

技术分享

二.实践与代码分析

作者在github上开源了代码:Fully Convolutional Networks

git clone https://github.com/shelhamer/fcn.berkeleyvision.org.git

我们首先将项目克隆到本地

项目文件结构很清晰,如果想train自己的model,只需要修改一些文件路径设置即可,这里我们应用已经train好的model来测试一下自己的图片:

我们下载voc-fcn32s,voc-fcn16s以及voc-fcn8s的caffemodel(根据提供好的caffemodel-url),fcn-16s和fcn32s都是缺少deploy.prototxt的,我们根据train.prototxt稍加修改即可。

-------------------------------------------------标签图像生成--------------------------------------------------------

VOCdevkit处下载VOC2012的训练/验证集,解压之后,在SegmentationClass文件夹下可以看到标签图像。

在PIL中,图像有很多种模式,如‘L‘模式,’P‘模式,还有常见的‘RGB‘模式,模式‘L‘为灰色图像,它的每个像素用8个bit表示,0表示黑,255表示白,其他数字表示不同的灰度。模式“P”为8位彩色图像,它的每个像素用8个bit表示,其对应的彩色值是按照调色板索引值查询出来的。标签图像的模式正是‘P‘模式,因此测试时要生成对应标签图像的图片的话,构建一个调色板即可。技术分享

按照上图,对应修改调色板和infer.py,就可以测试我们自己的图片了

import numpy as np
from PIL import Image
import caffe

# load image, switch to BGR, subtract mean, and make dims C x H x W for Caffe
im = Image.open(‘data/pascal/VOCdevkit/VOC2012/JPEGImages/2007_000129.jpg‘)
in_ = np.array(im, dtype=np.float32)
in_ = in_[:,:,::-1]
in_ -= np.array((104.00698793,116.66876762,122.67891434))
in_ = in_.transpose((2,0,1))

# load net
net = caffe.Net(‘voc-fcn8s/deploy.prototxt‘, ‘voc-fcn8s/fcn8s-heavy-pascal.caffemodel‘, caffe.TEST)
# shape for input (data blob is N x C x H x W), set data
net.blobs[‘data‘].reshape(1, *in_.shape)
net.blobs[‘data‘].data[...] = in_
# run net and take argmax for prediction
net.forward()
out = net.blobs[‘score‘].data[0].argmax(axis=0)

arr=out.astype(np.uint8)
im=Image.fromarray(arr)

palette=[]
for i in range(256):
    palette.extend((i,i,i))
palette[:3*21]=np.array([[0, 0, 0],
                            [128, 0, 0],
                            [0, 128, 0],
                            [128, 128, 0],
                            [0, 0, 128],
                            [128, 0, 128],
                            [0, 128, 128],
                            [128, 128, 128],
                            [64, 0, 0],
                            [192, 0, 0],
                            [64, 128, 0],
                            [192, 128, 0],
                            [64, 0, 128],
                            [192, 0, 128],
                            [64, 128, 128],
                            [192, 128, 128],
                            [0, 64, 0],
                            [128, 64, 0],
                            [0, 192, 0],
                            [128, 192, 0],
                            [0, 64, 128]], dtype=‘uint8‘).flatten()

im.putpalette(palette)
im.show()
im.save(‘test.png‘)

如上述,对代码的主要修改是增加了一个调色板,将L模式的图像转变为P模式,得到类似标签图像的图片。

接下来,只需要修改script中的图片路径和model的路径,就可以测试自己的图片了:

技术分享---------------------------------------------------原测试结果---------------------------------------------------------

技术分享-----------------------------------------------------P模式测试结果--------------------------------------------------
这是我跑出来的最终结果,可以看出skip architecture对最终的结果确实有优化作用。

这里没有对最终结果上色,按照VOC的颜色设置之后,就可以得到和论文中一模一样的结果了

下面是测试其他一些图片的结果:

技术分享技术分享

--------------------------------------------以下为对FCN中关键代码的分析-------------------------------------

这里我们着重分析在VOC数据集上的代码,其他几个数据集上的代码类似。

首先我们下载VOC2012数据集(根据data/pascal/README.md文件下载),从中可以看到图像(JPEGImages)和ground truth(SegmentationClass).

看过了数据集之后,我们去voc-fcn32s下看一下train.prototxt,你会发现它的输入层是作者自定义的python layer,也就是voc_layers.py里面定义的,所以,我们来看一下voc_layers.py里面的内容:

class VOCSegDataLayer(caffe.Layer):
    """
    Load (input image, label image) pairs from PASCAL VOC
    one-at-a-time while reshaping the net to preserve dimensions.

    Use this to feed data to a fully convolutional network.
    """

    def setup(self, bottom, top):
        """
        Setup data layer according to parameters:

        - voc_dir: path to PASCAL VOC year dir
        - split: train / val / test
        - mean: tuple of mean values to subtract
        - randomize: load in random order (default: True)
        - seed: seed for randomization (default: None / current time)

        for PASCAL VOC semantic segmentation.

        example

        params = dict(voc_dir="/path/to/PASCAL/VOC2011",
            mean=(104.00698793, 116.66876762, 122.67891434),
            split="val")
        """
        # config
        params = eval(self.param_str)
        self.voc_dir = params[‘voc_dir‘]
        self.split = params[‘split‘]
        self.mean = np.array(params[‘mean‘])
        self.random = params.get(‘randomize‘, True)
        self.seed = params.get(‘seed‘, None)

        # two tops: data and label
        if len(top) != 2:
            raise Exception("Need to define two tops: data and label.")
        # data layers have no bottoms
        if len(bottom) != 0:
            raise Exception("Do not define a bottom.")

        # load indices for images and labels
        split_f  = ‘{}/ImageSets/Segmentation/{}.txt‘.format(self.voc_dir,
                self.split)
        self.indices = open(split_f, ‘r‘).read().splitlines()
        self.idx = 0

        # make eval deterministic
        if ‘train‘ not in self.split:
            self.random = False

        # randomization: seed and pick
        if self.random:
            random.seed(self.seed)
            self.idx = random.randint(0, len(self.indices)-1)


    def reshape(self, bottom, top):
        # load image + label image pair
        self.data = self.load_image(self.indices[self.idx])
        self.label = self.load_label(self.indices[self.idx])
        # reshape tops to fit (leading 1 is for batch dimension)
        top[0].reshape(1, *self.data.shape)
        top[1].reshape(1, *self.label.shape)


    def forward(self, bottom, top):
        # assign output
        top[0].data[...] = self.data
        top[1].data[...] = self.label

        # pick next input
        if self.random:
            self.idx = random.randint(0, len(self.indices)-1)
        else:
            self.idx += 1
            if self.idx == len(self.indices):
                self.idx = 0


    def backward(self, top, propagate_down, bottom):
        pass


    def load_image(self, idx):
        """
        Load input image and preprocess for Caffe:
        - cast to float
        - switch channels RGB -> BGR
        - subtract mean
        - transpose to channel x height x width order
        """
        im = Image.open(‘{}/JPEGImages/{}.jpg‘.format(self.voc_dir, idx))
        in_ = np.array(im, dtype=np.float32)
        in_ = in_[:,:,::-1]
        in_ -= self.mean
        in_ = in_.transpose((2,0,1))
        return in_


    def load_label(self, idx):
        """
        Load label image as 1 x height x width integer array of label indices.
        The leading singleton dimension is required by the loss.
        """
        im = Image.open(‘{}/SegmentationClass/{}.png‘.format(self.voc_dir, idx))
        label = np.array(im, dtype=np.uint8)
        label = label[np.newaxis, ...]
        return label

里面定义了两个类,我们只看其中一个,该数据层继承自caffe.Layer,因而必须重写setup(),reshape(),forward(),backward()函数。

setup()是类启动时该做的事情,比如层所需数据的初始化。

reshape()就是取数据然后把它规范化为四维的矩阵。每次取数据都会调用此函数,load_image()很容易理解,就是转化为caffe的标准输入数据,我们重点关注一下load_label()这个方法,我们会发现这里的label不同于以往的分类标签,而是一个二维的label,也就是SegmentationClass文件夹中的ground truth图片(这里很好奇这样的图片是怎么生成的,我后来测试时发现并不能直接生成这样的图片,希望有知道的知友告知一下)

原来的图片是这样:

技术分享

但是我们按照上面的方法把这张图片load进来之后,label就是一个(1,500,334)的二维数据(严格来说是三维),每个位置的数值正是原图在这个位置的类别,原图是这样的:

技术分享注:255是边界数据,在训练时会被忽略

forward()就是网络的前向运行,这里就是把取到的数据往前传递,因为没有其他运算。 

backward()就是网络的反馈,data层是没有反馈的,所以这里就直接pass。

评论中有很多知友问到如何训练自己的数据,其实分析到这里就可以发现,大致可以以下三步:

1.为自己的数据制作label;

2.将自己的数据分为train,val和test集;

3.仿照voc_lyaers.py编写自己的输入数据层

但其实第一步还是挺困难的~~T_T

接下来我们分析一下论文中的关键结构:上采样和跳跃结构

ok,我们还是先从简单的结构开始,voc-fcns/train.prototxt

这里推荐使用Netscope进行网络结构的可视化

里面有这几个地方比较难以理解:

技术分享1.为什么首层卷积层要pad 100像素?

要解决这个问题,我们先来推算顺着结构推算一下:(假设是一般的VGG16结构,第一个卷积层只pad 1)

我们假设输入图片的高度是h,根据我们的卷积公式

conv1_1: 技术分享

conv1_2: 技术分享

。。。。。。

我们发现,VGG中缩小输出map只在池化层,所以下面我们忽略卷积层:

pool1: 技术分享

pool2: 技术分享

pool3: 技术分享

。。。。。。

pool5: 技术分享

很明显,feature map的尺寸缩小了32倍,接下来是卷积化的fc6层,如下图

技术分享fc6: 技术分享,

接下来还有两个卷积化的全连接层,fc7以及score_fr,但他们都是1*1的卷积核,对输出的尺寸并不会有影响,所以最终在输入反卷积之前的尺寸就是技术分享!

推导到这里pad 100的作用已经很明显了,如果不进行padding操作,对于长或宽不超过192像素的图片是没法处理的,而当我们pad 100像素之后,再进行以上推导,可以得到:

技术分享,这样就解决了以上问题,但是毋庸置疑,这会引入很多噪声。

技术分享2.反卷积层和Crop层如何产生和原图尺寸相同的输出?
卷积和反卷积在解读部分已经做了详细介绍,这里不再赘述,只是再重复一下公式,在卷积运算时,

技术分享

技术分享

有这样一组公式;而在上面我们提到,反卷积就是卷积计算的逆过程,所以在做反卷积时,技术分享技术分享

显然输出的高度和宽度应该这样计算;下面我们继续上面的推导,

upscore: 技术分享

反卷积之后,输出尺寸技术分享与原图像不一致,接下来就是Crop层起作用的时候了

我们首先了解一下Caffe中的Crop层

注:以下解读来自caffe-users中Mohamed Ezz的回答

---------------------------------------------------------------------------------------------------------------------------

为了理解Crop层,我们首先回答一个问题:Caffe是如何知道在哪里开始裁剪?

很显然Caffe并不知道,应该是程序员为它指定的,right,我们正是通过offset参数来指定。

下面我们举一个例子:

Caffe中的Blob是4D数据:(N,C,H,W),Corp层有两个bottom blob,也就是两个输入,第一个是要crop的blob,假设为blob A:(20,50,512,512),第二个是参考的blob,假设为blob B:(20, 10, 256, 256),还有一个top blob,亦即输出,假设blob C:(20,10,256,256)。

很显然crop的作用就是参考B裁剪A。

在这个例子中,我们只想裁剪后三个维度,而保持第一个维度不变,所以我们需要指定axis=1,表明我们只想裁剪从1开始的所有维度;

接下来我们需要指定offset参数,保证正确crop.有两种指定方式:

1.为想要crop的每个维度指定特定值,比如在这里我们可以指定offset=(25,128,128),这样在实际crop过程中:C = A[: , 25: 25+B.shape[1] , 128: 128+B.shape[2] , 128: 128+B.shape[3] ],换句话说,就是我们只保留A第二维中25-35的部分而放弃了其他的,以及二维图中的中间部分

2.只指定一个offset值,假设我们offest=25,这样在上例中我们相当于指定了offset=(25,25,25),也就是为所有要crop的维度指定一个相同的值

---------------------------------------------------------------------------------------------------------------------------

好了,了解了这些之后我们再来看一下刚才的Crop层,现在很明白了,我们参考data层将upscore层裁剪成score层,参数axis=2,表明我们只想裁剪后两个维度,亦即输出尺寸,offset=19,对应于C = A[: , : , 19: 19+h , 19: 19+h ],这正是从(h+38,w+38)的upscore层中裁剪出中间的(h,h)的图像,这也就产生了和原图尺寸相同的最终输出!

可以看出,这中间的每一个设置都是独具匠心的,让人不得不生出敬意。

但是只是用pool5层进行上采样最后产生的结果是比较粗糙的,所以作者又将不同层级的池化层分别上采样,然后叠加到一起,这样产生了更好的结果。当然理解上述之后,大家可以自行分析FCN16s和FCN8s,这里就不再赘述了!

三.总结

图像语义分割可能是自动驾驶技术下一步发展的一个重要突破点,而且本身也特别有意思!借此机会也学习了一下Caffe计算卷积和转置卷积的过程,对语义分割也有了一个初步的了解,收获颇丰!

「真诚赞赏,手留余香」
6 人赞赏
技术分享
技术分享
技术分享
技术分享
技术分享
技术分享
文章被以下专栏收录
122 条评论
写下你的评论...
 
 
技术分享
请问里面哪是训练自己的数据啊?
1 年前
技术分享
余俊(作者)回复armsoflove
首先向data里面添加自己的数据集,不知道你的数据集格式是否和作者提供的那几个一致,如果与VOC一致的话,你修改voc_layers.py就可以了,然后用voc-fcn8s文件夹下的配置进行训练即可
1 年前
技术分享
给答主点赞!?????
1 年前
技术分享
余俊(作者)回复pypy
多谢支持
1 年前
技术分享
卷积化那部分,输出的纬度不是1×1吧
1 年前
技术分享
余俊(作者)回复张运明
是的,已经修改,谢谢提醒
1 年前
技术分享
mark
1 年前
技术分享
讲的深入浅出。赞
1 年前
技术分享
楼主你好,按我的理解,在up sample 前最后的一层(就是通道数为1000的那一层)尺寸应该不是固定的,它的尺寸应该与输入图像的大小有关。请问我的理解正确么?
1 年前
技术分享
余俊(作者)回复庞小文
对的,deconvolution配合crop层得到和输入图片大小相同的map,输入图像的尺寸不固定,可以是任意的size
1 年前
推荐阅读










深度学习---从入门到放弃pytorch基础(代码片段)

深度学习—从入门到放弃(一)pytorchTensor类似于numpy的array,pandas的dataframe;在pytorch里的数据结构是tensor,即张量tensor简单操作1.Flattenandreshape###Originalz:tensor([[0,1],[2,3],[4,5],[6,7],[8,9],[10,11]])Flatte 查看详情

深度学习---从入门到放弃优化器(代码片段)

深度学习—从入门到放弃(四)优化器1.案例引入-MNIST手写数字识别现代深度学习优化中的许多核心思想(和技巧)可以在训练MLP以解决图像分类任务的中进行说明。在这里我们使用的是手写数字的MNIST数据集࿰... 查看详情

深度学习---从入门到放弃简单线性神经网络(代码片段)

深度学习—从入门到放弃(二)简单线性神经网络1.基本结构就像昨天说的,我们构建深度学习网络一般适用于数据大,处理难度也大的任务,因此对于网络的结构需要有一个非常深入的了解。这里以一个分类... 查看详情

初识pytorch:从安装到入门,从入门到放弃(代码片段)

...置安装验证PyTorchPyTorch是Facebook团队于2017年1月发布的一个深度学习框架,虽然晚于TensorFlow,也没有TensorFlow火,但目前已经与TensorFlow奇虎相当。而且PyTorch采用了Python语言的接口,可以说它才是Python程序员最容易上... 查看详情

ai框架核心技术系列来啦!从入门到放弃!

...术】这个系列,主要是跟大家一起探讨和学习人工智能、深度学习的计算机系统设计,而整个系统是围绕着我在工作之余所积累、梳理、构建关于AI框架的一些核心技术内容。【AI框架核心技术】这个系列,主要是跟大家一起探... 查看详情

[补档][从入门到放弃]——网络流学习索引

前篇由于某篇博文已经长到我自己看不下去的地步,又不忍心删,所以就有了这篇索引,方便自己找,也方便来访的客人看嘛(真的会有人来看吗啊喂) 最长的博文2017-7-29大佬讲课笔记网络流——从入门到放弃没错,就是它... 查看详情

机器学习从入门到放弃

...面增补的电子版看ng视频,推荐斯坦福的黑板教学图解机器学习,李航的统计学习,ISLR周志华那本真 查看详情

凸优化从入门到放弃(目录)

本篇教程采用BoydandVandenberghe的教材,主要是自己学习凸优化课程时候的笔记和心得,其中应用篇没有学习。凸优化从入门到放弃00-凸优化引言01-凸集02-凸函数(暂无)03-凸优化问题(暂无)04-对偶(暂无)05-无约束优化算法(... 查看详情

nginx从入门到放弃

前面我们学习了nginx的基本操作和日志管理,今天我们学习一下生产环境经常会用到的路由定位location设置,在工作中,经常可能会出现怎么设置的路由访问不到网页呀?总是出现404错误啊,这些都很有可能是location的配置有误所... 查看详情

clickhouse从入门到放弃(代码片段)

...clickhouse做数据分析,我赶紧从docker上下载一个,学习学习。学习成本不大,会mysql就行。clickhouse下载分区基本使用下载下载clickhouse包dockerpullyandex/clickhouse-clientdockerpullyandex/clickhouse-server启动clickhouse-serv 查看详情

clickhouse从入门到放弃(代码片段)

...clickhouse做数据分析,我赶紧从docker上下载一个,学习学习。学习成本不大,会mysql就行。clickhouse下载分区基本使用下载下载clickhouse包dockerpullyandex/clickhouse-clientdockerpullyandex/clickhouse-server启动clickhouse-serv 查看详情

学习jvm是如何从入门到放弃的?

学习JVM的目的也很简单:能够知道JVM是什么,为我们干了什么,具体是怎么干的。能够理解到一些初学时不懂的东西在面试的时候有谈资能装逼  (图片来源:zhuanlan.zhihu.com/p/25511795,侵删)声明:全文默认指的是HotSpotVM一、... 查看详情

《java从入门到放弃》文章目录

...完,所以先按时间顺序排列,等相关内容都写完后,再按学习顺序来整理。《Java从入门到放弃》入门篇:XMLHttpRequest的基本用法《Java从入门到放弃》入门篇:Struts2的基本访问方《Java从入门到放弃》入门篇:Struts2的基本访 查看详情

docker从入门到放弃(代码片段)

??为什么要学习docker呢?深有体会,由于一些原因只能在他人电脑上搭建环境,明明在自己电脑上的程序跑的好好的,在他人的电脑上就是死活出错。折磨人呀!!!!!可是能怎么办,工作还得继续,曲线救国呗,折腾了一天... 查看详情

小白学习h5从入门到放弃

话不多说直接进入主题:1.什么是HTML语言HTML5是用于取代1999年所制定的HTML4.01和XHTML1.0标准的HTML标准版本,现在仍处于发展阶段,但大部分浏览器已经支持某些HTML5技术。HTML5有两大特点:首先,强化了Web网页的表现性能。其次,... 查看详情

初识pytorch:从安装到入门,从入门到放弃(代码片段)

...置安装验证PyTorchPyTorch是Facebook团队于2017年1月发布的一个深度学习框架,虽然晚于TensorFlow,也没有TensorFlow火,但目前已经与TensorFlow奇虎相当。而且PyTorch采用了Python语言的接口,可以说它才是Python程序员最容易上... 查看详情

爬虫从入门到放弃-纯新手学习-爬虫基本数据库安装

 1.安装好前期必备的库-requests向网页发出请求解释器自带的urllib和reselenium用于向有js渲染的网页发起请求fromseleniumimportwebdriverdriver=webdriver.Chrome()#生成一个driver对象,并打开谷歌浏览器driver.get(‘https://www.baidu.com‘) #打开... 查看详情

typescript从入门到放弃

...文已收录至https://github.com/likekk/-Blog欢迎大家star??????,共同学习,共同进步。如果文章有错误的地方,欢迎大家指出。后期将在将github上规划前端学习的路线和资源分享。前言亲爱的读者们,大家好,我是杨戬,一个在互联网前端... 查看详情