大白话5分钟带你走进人工智能-第36节神经网络之tensorflow的前世今生和DAG原理图解(4)
目录
1、Tensorflow框架简介
Tensorflow由Google Brain谷歌大脑开源出来的,在2015年11月在GitHub上开源,2016年是正式版,2017年出了1.0版本,趋于稳定。谷歌希望让优秀的工具得到更多的去使用,所以它开源了,从整体上提高深度学习的效率。在Tensorflow没有出来之前,有很多做深度学习的框架,比如caffe,CNTK,Theano,公司里更多的用Tensorflow。caffe在图像识别领域也会用。Theano用的很少,Tensorflow就是基于Theano。中国的百度深度学习PaddlePaddle也比较好,因为微软、谷歌、百度它们都有一个搜索引擎,每天用户访问量非常大,可以拿到用户海量的数据,就可以来训练更多的模型。
Tensorflow发展非常快,它是适合所有人的开放源代码机器学习框架。是一个开放源代码软件库,用于进行高性能数值计算,借助其灵活的架构,然后用户可以轻松的将计算工作部署在多个平台(CPU,GPU,TPU)。GPU是显卡,有两个厂商,一个是N卡,一个是AMD,其中AMD的显卡是不支持Tensorflow运行在GPU上的,TPU是谷歌自己出的一个硬件,它可以更好的跑深度学习。Tensorflow可以部署在多数平台里面去,一般是用CPU,GPU,TPU去训练,也可以把这个模型训练好之后,把它扔到手机端,让手机端去加载这个模型,进行一些预测,比如说图像识别等等。它的兼容性是非常好的。
Tensorflow可为机器学习和深度学习提供强有力支持。Tensorflow虽然是一个专门做深度学习的,但是它来做机器学习也非常easy,而且它还在不断的封装一些库,一些机器学习的方法、算法,它发展的非常快。所以未来你只会一套Tensorflow,你就可以即处理机器学习,又处理深度学习。并且其灵活的数值计算核心广泛应用于其它科学领域,比如在Python里面的一些数值计算,很多时候会用NumPy, Tensorflow里面也有一些API它可以取代NumPy。
当下Tensorflow是杀出重围的,在关注度,用户数上都占据绝对优势,有一统天下之势。因为Tensorflow是一个开源项目,只有更多的高手贡献相关的开源代码,这个社区才会变得越来越好,所以contributor数量是至关重要的,也就意味着Tensorflow的版本会更新得非常快。
另外谷歌的在业界的号召力确实很强大,所以很多人相信Tensorflow非常好,谷歌在去年出了一个东西叫做Alpha Go,下围棋很厉害,同时开源了Tensorflow,说Alpha Go是基于Tensorflow开发出来的,给Tensorflow做一个大的广告。Alpha Go是谷歌收购的,之前是英国的一个公司,开源出的一个项目,然后谷歌把公司给收购了。其实很多公司都会做这种事情,之前我去一个公司做企业分享的时候,跟那里面的部门老大,他很厉害,会做很多人工智能的摄像头,包括图像识别,语音识别,视频对话,视频加密等等,原来在阿里,后来自己出来开了一公司,做智能摄像头,然后干得不错,让一个知名公司给收购了。
Tensorflow是一个相对高阶的机器学习库,它可以很方便的让用户去设计神经网络的结构,而不必为了追求高效率而亲自写C++或CUDA代码。如果在Tensorflow里面去做深度学习,并且有一些时间,你可以去研究C++或CUDA代码,因为有些公司在面试的时候,它需要有些人去写C++或CUDA代码,为什么?因为同样一份代码用C++或CUDA代码来写,执行效率会稍微的高一些,它少了python里面解释和翻译的过程,它的代码跑起来会更快。
Tensorflow和Theano一样都支持自动求导,这件事情很重要,Tensorflow是基于Theano的,Theano带的功能叫自动求导。如果这个框架没有带自动求导的功能,在做机器学习迭代过程当中,为了去调整w,首先需要把梯度求出来,求梯度公式就得自己实现。如果很多用户对公式推导不太熟悉的话,或者推导错的话,代码就错了。那对于Tensorflow来说,就不要自己去实现了,直接搞到Loss损失函数是什么,它自动把偏导求出来,不需要再通过反向传播,一步步的求解梯度。
Tensorflow另外一个重要的特点,就是它灵活的移植性,它可以在cpu上面或gpu上面跑,同样的代码也可以在移动设备上面来执行。但是可以把执行好的模型加载到里面去predict预测。因为其核心的代码和Caffe一样是用C++编写的,它也可以让手机这种内存和CPU资源都紧张的设备可以来运行复杂的模型。所以Tensorflow有C++接口,也有python、Go、Java接口,只不过python用的更多一些。
Tensorflow里面也内置有tf.learn和TF.slim等上层组件,可以帮助快速的设计新网络。比如tf.learn里面就封装了机器学习的一些算法;TF.slim就是一个组件,可以很好的把之前别人训练的模型给读进来。这样的话你就可以更快速的调机器学习的库,或者用别人的模型,在别人模型基础上设计新网络。并且它兼容Scikit-learn里面estimator接口,可以很方便实现Scikit-learn里面的evaluate、grid search、cross validation功能,都可以很好的兼容。
Tensorflow不只局限于神经网络,只要是数据流式图支持的自由表达的算法表达,Tensorflow都可以来实现。
Tensorflow还有强大的可视化的组件叫TensorBoard,有TensorBoard之后,就可以把你写代码非常清晰的看到有效图,可以看到它的流程,包括它实时运算的时候,它怎么跑的,整个都可以通过它进行监控。它能可视化网络结构和训练过程,对于观察复杂的网络结构和监控长时间、大规模训练很有帮助。
2、安装Tensorflow
如果安装在windows里,32位,64位都可以。
关于CPU版本安装,使用pip安装。 用完这个代码之后就可以跑了。
如果显卡是英伟达的,可以选择安装GPU。如果显卡不是很旧的话,可以安装CUDA 9.0版本的。然后再安装cuDNN, DNN是深度神经网络,它是一个小的包,它里面会有dll的一些东西,可以整合CUDA和底层的系统。
GitHub网址:github.com/tensorflow/tensorflow
模型仓库网址: github.com/tensorflow/models
深度学习有很多经典的模型,它不是靠公司那点数据就能训练好得,是别人通过海量的数据训练完之后,把这个模型开源出来了,很多公司就会拿着别人的模型,然后再来修改,踩着别人的肩膀再来进行训练。相当于别人的模型作为你们w的初始值。
如果没有别人的模型,一般初值都是随机的,如果别人开源一些模型之后,我们就可以把别的模型作为初始值,再来进行训练,速度会快很多。比如resnet残差网络,就是一个经典的模型;比如keras_application,mnist等一些模型。 推荐也可以用深度学习来做。这个里有一些比较经典的模型,我们可以去使用。
除了执行深度学习算法,Tensorflow还可以用来实现很多其它算法,比如可以去实现线性回归,逻辑回归,或者是随机森林等。很多算法都有封装。
3、核心概念
TensorFlow中的计算可以表示为一个有向图(Directed Graph),或者称计算图(Computation Graph),其实就是构建一个从前往后,正向传播的一个逻辑,称为构建一个计算图。比如如下的一个计算图:
其中每一个运算操作(operation)(图中的圆圈)将作为一个节点(node),在计算图里面,每个节点就是一个逻辑,一个function代码,比如相乘还是相加。这里的每个节点可以把它看成是一个神经元,它可以有很多的输入和输出。每一个节点描述了一种运算操作。
计算图描述数据的计算流程,也负责维护和更新状态。其实做机器学习、做人工智能要的模型最终就是一堆参数,这些参数本质上就是w1到wn,所以维护和更新的状态,就是维护这些w,不断的迭代更新。
用户通过python,c++,go,Java语言设计这个这个数据计算的有向图。计算图中的边,里面流动的数据称为叫张量,data1,data2,就是流动在边(图中箭头)里面的数据。张量就是多维数组,因为x训练集里面有很多个维度,是一个m行n列的x数据集,所以它就是一个多维数组。数组在整个计算图里面流动起来,所以得名Tensorflow,在流动的过程中,数据就不断的变化,然后循环往复不断地流动,就不断的调整连线所对应的权重值。
4、代码实例和详细解释
看下一般tensorflow的流程
import tensorflow as tf
b = tf.Variable(tf.zeros([100]))
W = tf.Variable(tf.random_uniform([784,100], -1, 1))
x = tf.placeholder(name="x")
relu = tf.nn.relu(tf.matmul(W, x) + b)
cost = [..]
sess = tf.Session()
for step in range(0, 10):
input = …construct 100-D input array…
result = sess.run(cost, feed_dict={x: input})
print(step, result)
解释下代码:
import tensorflow as tf
如果想用Tensorflow这个框架,首先需要把它导进来,一般情况给Tensorflow起个别名叫tf。
b = tf.Variable(tf.zeros([100]))
因为我们的算法是y=ax+b,其中b代表截距bias,bias有多少个,取决于这一层有多少神经元。tf.zeros和机器学习的代码非常类似,np.zeros就是来一个数组,里面有一堆0。 tf.zeros[100]相当于创建长度为100,里面每个位置都为0的一个向量。然后把它作为tf.Varialbe变量,为什么要把bias变成变量?因为在迭代过程中要反复调整它。
W = tf.Variable(tf.random_uniform([784,100], -1, 1))
w=tf.Varialbe,我们想要得到w矩阵,它也是一个变量,因为在每次迭代过程中要去调里面的每个值。它是784行100列,就意味着它前一层有784个神经元,后一层有100个神经元,这两层之间的w矩阵就是784行100列。 这里有100个神经元,所以截距bias里面写的是100,相当于每个线上都有一个单独的bias,都是1。
w最开始需要随机,random_uniform,是均匀分布,意思是每随机其中一个数的时候,在-1到1之间,它的概率都是相同的,叫做均匀分布。根据均匀分布来随机取值,w矩阵是784行100列,它有78400个数据需要随机出来,里面的每一个数是通过随机得到的,通过uniform均匀分布的方式来得到。
如果把它变成正态分布,我们每一次取-1到+1到一之间值的时候,0概率最大。
x = tf.placeholder(name="x")
x是谁我们不知道,因为Tensorflow主要分为两部分,第一步是来构建一个计算图;第二步是迭代开始算w或者bias截距。所以在构建计算图的时候,还不知道x是谁,也不知道y是谁,所以x=tf.placeholder(name="x")意思是先占个位置。
relu = tf.nn.relu(tf.matmul(W, x) + b)
接着要去构建正向传播,计算图。我们看下这里的示意图:
W构建为784*100,意思是我们在构建一个输入层和隐藏层之间的w矩阵,相当于输入层有784个神经元(x1到xn的个数),下一层隐藏层,有100个神经元,中间的w矩阵,就是784行100列,所以这样写代码的话,就意味知道前面一层有多少神经元,后面一层有多少神经元。
matmul(W,X)是w矩阵和x矩阵相乘,就是对应的位置相乘相加。x矩阵是样本集,当把第一行抽出来的时候,第一行x1到xn共784个数据也就是1行784列即[1,784]代入到网络拓扑里面,x传进去就会和w784行100列即[784,100],输出层得到的是1行100列的结果。这是对一条样本进行预测,输出的就是这条样本的预测值。也可以对m个样本进行预测,输出就是m个样本的预算值。相当于x是m*784,w是784×100,输出层的结果是m*100。
前面b,w,x就作为输入。+b,b是100个值,就是让输出的结果m行100列里面的每一个值分别加上bias截距。最后形状没变,还是m行100列。接着再去经过tf.nn.relu,relu是max(0,z),但在Tensorflow里面不需要去实现这个公式,它会封好tf.nn.relu里面。
到这里为止,完成了一个神经元里面的加和。前面有784个输入,784个输入分别去和w矩阵相乘相加;每个神经元身上还有一个截距,最后再把截距加上。因为最后还需要一个非线性的function才能完成,所以再接一个tf.nn.relu。
cost = [..]
有了最终的结果的输出,就可以算Cost的损失。
前面这一部分在做构建计算图。
下面一部分
sess = tf.Session()
tf.session相当于是来一个会话的上下文,相当于有一个Tensorflow环境。
for step in range(0, 10):
input = …construct 100-D input array…
result = sess.run(cost, feed_dict={x: input})
print(step, result)
For…in…是迭代,range(0,10)会迭代十次,在每一次迭代里面sess.run,在每一次迭代的时候,计算一下cost损失,最后把损失结果打印输出一下。
Tensorflow的流程就是先来构建计算图,然后把数据传进来,算到最后的损失。
5、拓扑图之有向无环图DAG
在tensorflow里最主要是构建一个有向图,下面这张图是一个有向无环图,解释了上面的代码。
解释下:先通过tf.bariable定义一个b,然后tf.bariable定义一个w,接着tf.placeholder定义一个x,它们都是tensor,就是有向图里面的b,w,x。然后把这些数据进行一个正向传播,用tf.matmul把w和x进行一个操作,Matmul是一个操作节点。
把w,x拿过来,它在Matmul里面进行相应计算,然后进入下一个节点add。把b截距拿过来,把它的结果进一步累加。
这个无环图里relu对应着代码tf.nn.relu,relu得到的结果,再经过其它的层次的传递,如果后面还有一些其它的神经网络层次,算出Cost就是最后一个节点。
每一个代码在tensorflow背后都会变成一个图,在构建图的过程当中,没有把数据传进来。它只是在前面b和w初始化了之后,x只是用来占个位置,并没有把真正的数据传给它,所以前面只是在构建正向传播的一个流程,并没有真正传数据。
如果说的更严谨一点,到b,w 的tf.bariable为止时候并没有去初始化。只有用sess.run(w. initialize)时才会真正地把调用的一些函数进行初始化。
如果创建了session,它就会把x读进来,把w,b初始化,然后再来进行有向无环图的计算,这样一个传播。
当我们调用sess.run[cost]的时候,它为了去计算出来cost,一定要一路追溯回去找b,w,x才能算出relu,然后把relu放进来,才能去算出cost。 所以如果为了去算出cost,前面没有对b,w,x进行初始化,它没法往下去执行,就会报错。
6、其他深度学习框架详细描述
其它深度学习的框架还有很多,其中包括TensorFlow、Caffe、 Keras、CNTK、Torch7、MXNet、Leaf、Theano、DeepLearning4、Lasagne、Neon。谷歌的caffe开源的,做卷积神经网络的框架;Keras基于Tensorflow、Theano以及CNTK更高一层又更深的封装的框架,就是Tensorflow100行干的事情,Keras可能10行就完了,更加简化代码。但是学好了Tensorflow,才能明白它真正是干什么,因为开始它并没有底层的实现,它是基于CNTK、Tensorflow、Theano的一个框架,所以你的环境里面必须得有CNTK、Tensorflow、Theano,然后再去装Keras这个模块才能去用。
CNTK是微软的,Torch7也是, MXnet是亚马逊的一个深度学习的框架,它们跟Tensorflow都非常的类似。
各大主流框架基本都支持Python,目前python在科学计算和数据挖掘领域可以说是独领风骚。就是在AI人工智能这个领域,Python应用是最多的,未来一定也是最多的。因为java干一切首先得创造一个类,然后去new一个对象,然后才能去调用这个类里面定义的一些方法,很麻烦。我们做人工智能的时候,可能用不到类和对象这些东西,很多时候函数编程就能解决。所以Python函数编程就用的会更多一些,而且Python语法更加简洁,它有更多非常方便的module模块,不需要自己开发,直接调用就可以。
而且Python可以非常方便地做一个项目,而R语言它也是做数据分析,做统计学,也可以做机器学习,R语言它就很难开发成一个web项目,很难去做数据库的连接,很难去做爬虫等等。Python的生态体系比较完善。
在数据挖掘工具链上,Python有Numpy,Scipy,Pandas,Scikit-learn,XGBoost等组件,这些东西在tensorflow里面一样可以拿来用,因为它都是Python执行的环境。
6.1 Caffe框架:
Convolutional Architecture for Fast Feature Embedding。
一个被广泛使用的开源深度学习框架,由伯克利视觉中心进行维护
特点一:容易上手,网络结构都是以配置文件形式定义,不需要用代码设计网络 。
特点二:训练速度快,能够训练state-of-the-art的模型与大规模的数据 。
特点三:组件模块化,可以方便地拓展到新地模型和学习任务上 。
Caffe核心概念是Layer,每一个神经网络地模块都是一个Layer。Layer接收输入数据,同时经过内 部计算产生输出数据。设计网络,需要把各个Layer拼接在一起构成完整地网络(通过写protobuf配 置文件定义)
比如卷积Layer,它的输入就是图片全部像素点,内部进行的操作是各种像素的值与Layer参数的 convolution操作,最后输出的是所有卷积核filter的结果。 每一个Layer需要定义两种运算,一种是正向forward运算,从输入数据计算出输出结果。Caffe的配置文件是一个类似JSON的prototxt文件,其中使用许多顺序连接的Layer来描述神经网络结构,在prototxt文件中设计网络结构比较受限,没有像TensorFlow或者Keras那样在python中设计网络 结构方便、自由。
配置文件不能用编程方式调整超参数,也没有像sklearn那样好用的estimator可以方便进行交叉验证、 超参数的Grid Search。
Caffe在GPU上性能很好,一个GTX 1080训练AlexNet一天可以训练上百万张图片,但是目前仅支 持单机多GPU训练,原生没有支持分布式训练,不过有CaffeOnSpark,借助雅虎开源的技术结合 spark分布式框架实现Caffe大规模分布式训练。
6.2 Theano框架:
与sklearn一样,Theano很好的整合了Numpy , 因为Theano非常流行,有许多人写了高质量文档和教程,用户方便查找Theano的各种FAQ,比如 如何保持模型,如何运行模型等,不过它更多被当作一个研究工具,而不是当作产品来使用。 Theano在单GPU上执行效率不错,性能和其他框架类似,但是运算时需要将用户的python代码转换成CUDA代码,再编译为二进制可执行文件,编译复杂模型时间非常久 , 不过,Theano在训练简单网络比如很浅的MLP时性能可以比TensorFlow好,因为全部代码都是运行时编译,不需要像TensorFlow那样每次喂给mini-batch数据时候都得通过低效的python循环来现,Theano是一个完全基于Python的符合计算库,不需要像Caffe一样为Layer写C++或CUDA代码, 用户定义各种运算,Theano可以自动求导,省去了完全手工写神经网络反向传播的麻烦,也不需要 像Caffe一样为Layer写C++或者CUDA代码。如果没有Theano,可能根本不会出现这么多好的python深度学习框架,就如没有python的科学计 算的基石Numpy,就不会有Scipy、Sklearn、SkImage,可以说Theano就是深度学习界的Numpy, 是其他各类python深度学习库的基石 。 但是直接使用Theano来设计大型的神经网络还是太繁琐了,用Theano实现Google Inception就 像用Numpy实现一个支持向量机SVM 。事实上,不需要 总是从最基础的tensor粒度开始设计网络,而是从更上层的Layer粒度来设计网络。
6.3 Keras框架:
Theano派生出了大量基于它的深度学习库,包括一系列的上层封装,其中大名鼎鼎的Keras对神经网络抽象得非常合适,以致于可以随意切换执行计算得后端(目前同时支持Theano和TensorFlow) 。 Keras比较适合在探索阶段快速地尝试各种网络结构,组件都是可插拔的模块,只需要将一个个组件, 比如卷积层、激活函数连接起来,但是设计新的模块或者新的Layer就不太方便了 它是高度模块化、极简的神经网络库,用python实现,同时运行在TensorFlow和Theano上,意在让用户进行最快的原型试验,让想法变为结果的过程最短 。Theano和TensorFlow的计算图支持更通用的计算,而Keras专精于深度学习 。 Theano和TensorFlow更像深度学习领域的Numpy,而Keras是深度学习的sklearn。神经网络、损失函数、优化器、初始化方法、激活函数、和正则化等模块都是可以自由组合的 Keras中的模型都是在python中定义的,不需要Caffe和CNTK等需要额外的配置文件来定义模型。
通过编程方式调试模型结构和各种超参数, 几行代码就可以实现一个MLP,十几行代码实现一个AlexNet,这在其它框架里面是不可能完成的任 务 。 Keras问题在于目前无法直接使用多GPU,所有对大规模的数据处理速度没有其他支持多GPU和分布 式的框架快。
大白话5分钟带你走进人工智能-第36节神经网络之tensorflow的前世今生和DAG原理图解(4)的更多相关文章
- 大白话5分钟带你走进人工智能-第32节集成学习之最通俗理解XGBoost原理和过程
目录 1.回顾: 1.1 有监督学习中的相关概念 1.2 回归树概念 1.3 树的优点 2.怎么训练模型: 2.1 案例引入 2.2 XGBoost目标函数求解 3.XGBoost中正则项的显式表达 ...
- 大白话5分钟带你走进人工智能-第31节集成学习之最通俗理解GBDT原理和过程
目录 1.前述 2.向量空间的梯度下降: 3.函数空间的梯度下降: 4.梯度下降的流程: 5.在向量空间的梯度下降和在函数空间的梯度下降有什么区别呢? 6.我们看下GBDT的流程图解: 7.我们看一个 ...
- 大白话5分钟带你走进人工智能-第35节神经网络之sklearn中的MLP实战(3)
本节的话我们开始讲解sklearn里面的实战: 先看下代码: from sklearn.neural_network import MLPClassifier X = [[0, 0], [1, 1]] ...
- 大白话5分钟带你走进人工智能-第二十节逻辑回归和Softmax多分类问题(5)
大白话5分钟带你走进人工智能-第二十节逻辑回归和Softmax多分类问题(5) 上一节中,我们讲 ...
- 大白话5分钟带你走进人工智能-第四节最大似然推导mse损失函数(深度解析最小二乘来源)(2)
第四节 最大似然推导mse损失函数(深度解析最小二乘来源)(2) 上一节我们说了极大似然的思想以及似然函数的意义,了解了要使模型最好的参数值就要使似然函数最大,同时损失函数(最小二乘)最小,留下了一 ...
- 大白话5分钟带你走进人工智能-第30节集成学习之Boosting方式和Adaboost
目录 1.前述: 2.Bosting方式介绍: 3.Adaboost例子: 4.adaboost整体流程: 5.待解决问题: 6.解决第一个问题:如何获得不同的g(x): 6.1 我们看下权重与函数的 ...
- 大白话5分钟带你走进人工智能-第二十九节集成学习之随机森林随机方式 ,out of bag data及代码(2)
大白话5分钟带你走进人工智能-第二十九节集成学习之随机森林随机方式 ,out of bag data及代码(2) 上一节中我们讲解了随机森林的基本概念,本节的话我们讲解随机森 ...
- 大白话5分钟带你走进人工智能-第三节最大似然推导mse损失函数(深度解析最小二乘来源)(1)
第三节最大似然推导mse损失函数(深度解析最小二乘来源) 在第二节中,我们介绍了高斯分布的 ...
- 大白话5分钟带你走进人工智能-第十四节过拟合解决手段L1和L2正则
第十四节过拟合解决手段L1和L2正则 第十三节中, ...
随机推荐
- Spring Cloud学习(一):Eureka服务注册与发现
1.Eureka是什么 Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的. Eureka ...
- Java中的单例模式(Singleton Pattern in Java)
Introduction 对于系统中的某个类来说,只有一个实例是很重要的,比如只有一个timer和ID Producer.又比如在服务器程序中,配置信息保留在一个文件中,这些配置信息由一个单例对象统一 ...
- 个人永久性免费-Excel催化剂功能第92波-地理地址与经纬度互转功能
GPS设备和手机LBS的兴起,在地理信息存储过程中,在程序.应用级别是需要用经纬度去定位,而在数据分析的级别,特别是省市区镇街的分析,用到的是人可识别的文本类型存储,从设备中采集下来的数据和人工维护的 ...
- docker实战(二)之redis的使用
docker中安装redis的步骤比较简单,Linux系统版本centos7.4 1.官方仓库https://hub.docker.com/r/library/redis/tags/中查看redis的 ...
- C#编程.函数.参数
详细内容请参见<C#入门经典(第4版)>p101页 1.参数匹配 在调用函数时,必须使参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的类型.个数.和顺序. 注:函数签名由函数的名称 ...
- python列表、元组、字典练习题
1.元素分类 有如下值集合[11,22,33,44,55,66,77,88,99,90], 将所有大于66的值保存至字典的第一个key中,将小于66值保存至第二个key的值中. li = [11,22 ...
- spark 源码分析之三 -- LiveListenerBus介绍
LiveListenerBus 官方说明如下: Asynchronously passes SparkListenerEvents to registered SparkListeners. 即它的功 ...
- .NET Core CSharp初级篇 1-8泛型、逆变与协变
.NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...
- thinkphp 多对多表查询
1.表 班级表classes 学生表student 中间表classes_students 2.使用模型关联查询 新建模型 Classes在里面添加代码 ClassesStudent中间表模型,可以不 ...
- vue使用video.js解决m3u8视频播放格式
今天被这个关于m3u8视频播放不了搞了一下午,这个项目所有的视频流都是m3u8格式的,后台给我们返回的都是m3u8格式的视频流,解决了好长时间,看了好多博客,只有这个博客给我点启发,去解决这个问题,请 ...