【深度学习系列】用PaddlePaddle和Tensorflow进行图像分类
上个月发布了四篇文章,主要讲了深度学习中的“hello world”----mnist图像识别,以及卷积神经网络的原理详解,包括基本原理、自己手写CNN和paddlepaddle的源码解析。这篇主要跟大家讲讲如何用PaddlePaddle和Tensorflow做图像分类。所有程序都在我的github里,可以自行下载训练。
在卷积神经网络中,有五大经典模型,分别是:LeNet-5,AlexNet,GoogleNet,Vgg和ResNet。本文首先自己设计一个小型CNN网络结构来对图像进行分类,再了解一下LeNet-5网络结构对图像做分类,并用比较流行的Tensorflow框架和百度的PaddlePaddle实现LeNet-5网络结构,并对结果对比。
什么是图像分类
图像分类是根据图像的语义信息将不同类别图像区分开来,是计算机视觉中重要的基本问题,也是图像检测、图像分割、物体跟踪、行为分析等其他高层视觉任务的基础。图像分类在很多领域有广泛应用,包括安防领域的人脸识别和智能视频分析等,交通领域的交通场景识别,互联网领域基于内容的图像检索和相册自动归类,医学领域的图像识别等(引用自官网)
cifar-10数据集
CIFAR-10分类问题是机器学习领域的一个通用基准,由60000张32*32的RGB彩色图片构成,共10个分类。50000张用于训练集,10000张用于测试集。其问题是将32X32像素的RGB图像分类成10种类别:飞机
,手机
,鸟
,猫
,鹿
,狗
,青蛙
,马
,船
和卡车。
更多信息可以参考CIFAR-10和Alex Krizhevsky的演讲报告。常见的还有cifar-100,分类物体达到100类,以及ILSVRC比赛的100类。
自己设计CNN
了解CNN的基本网络结构后,首先自己设计一个简单的CNN网络结构对cifar-10数据进行分类。
网络结构
代码实现
1.网络结构:simple_cnn.py
#coding:utf-8
'''
Created by huxiaoman 2017.11.27
simple_cnn.py:自己设计的一个简单的cnn网络结构
''' import os
from PIL import Image
import numpy as np
import paddle.fluid as fluid
from paddle.trainer_config_helpers import * with_gpu = os.getenv('WITH_GPU', '') != '' def simple_cnn(img):
conv_pool_1 = paddle.networks.simple_img_conv_pool(
input=img,
filter_size=5,
num_filters=20,
num_channel=3,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
conv_pool_2 = paddle.networks.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=50,
num_channel=20,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
fc = paddle.layer.fc(
input=conv_pool_2, size=512, act=paddle.activation.Softmax())
2.训练程序:train_simple_cnn.py
#coding:utf-8
'''
Created by huxiaoman 2017.11.27
train_simple—_cnn.py:训练simple_cnn对cifar10数据集进行分类
'''
import sys, os import paddle.v2 as paddle
from simple_cnn import simple_cnn with_gpu = os.getenv('WITH_GPU', '') != '' def main():
datadim = 3 * 32 * 32
classdim = 10 # PaddlePaddle init
paddle.init(use_gpu=with_gpu, trainer_count=7) image = paddle.layer.data(
name="image", type=paddle.data_type.dense_vector(datadim)) # Add neural network config
# option 1. resnet
# net = resnet_cifar10(image, depth=32)
# option 2. vgg
net = simple_cnn(image) out = paddle.layer.fc(
input=net, size=classdim, act=paddle.activation.Softmax()) lbl = paddle.layer.data(
name="label", type=paddle.data_type.integer_value(classdim))
cost = paddle.layer.classification_cost(input=out, label=lbl) # Create parameters
parameters = paddle.parameters.create(cost) # Create optimizer
momentum_optimizer = paddle.optimizer.Momentum(
momentum=0.9,
regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),
learning_rate=0.1 / 128.0,
learning_rate_decay_a=0.1,
learning_rate_decay_b=50000 * 100,
learning_rate_schedule='discexp') # End batch and end pass event handler
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "\nPass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
else:
sys.stdout.write('.')
sys.stdout.flush()
if isinstance(event, paddle.event.EndPass):
# save parameters
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
parameters.to_tar(f) result = trainer.test(
reader=paddle.batch(
paddle.dataset.cifar.test10(), batch_size=128),
feeding={'image': 0,
'label': 1})
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) # Create trainer
trainer = paddle.trainer.SGD(
cost=cost, parameters=parameters, update_equation=momentum_optimizer) # Save the inference topology to protobuf.
inference_topology = paddle.topology.Topology(layers=out)
with open("inference_topology.pkl", 'wb') as f:
inference_topology.serialize_for_inference(f) trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(
paddle.dataset.cifar.train10(), buf_size=50000),
batch_size=128),
num_passes=200,
event_handler=event_handler,
feeding={'image': 0,
'label': 1}) # inference
from PIL import Image
import numpy as np
import os def load_image(file):
im = Image.open(file)
im = im.resize((32, 32), Image.ANTIALIAS)
im = np.array(im).astype(np.float32)
# The storage order of the loaded image is W(widht),
# H(height), C(channel). PaddlePaddle requires
# the CHW order, so transpose them.
im = im.transpose((2, 0, 1)) # CHW
# In the training phase, the channel order of CIFAR
# image is B(Blue), G(green), R(Red). But PIL open
# image in RGB mode. It must swap the channel order.
im = im[(2, 1, 0), :, :] # BGR
im = im.flatten()
im = im / 255.0
return im test_data = []
cur_dir = os.path.dirname(os.path.realpath(__file__))
test_data.append((load_image(cur_dir + '/image/dog.png'), )) # users can remove the comments and change the model name
# with open('params_pass_50.tar', 'r') as f:
# parameters = paddle.parameters.Parameters.from_tar(f) probs = paddle.infer(
output_layer=out, parameters=parameters, input=test_data)
lab = np.argsort(-probs) # probs and lab are the results of one batch data
print "Label of image/dog.png is: %d" % lab[0][0] if __name__ == '__main__':
main()
3.结果输出
I1128 21:44:30.218085 14733 Util.cpp:166] commandline: --use_gpu=True --trainer_count=7
[INFO 2017-11-28 21:44:35,874 layers.py:2539] output for __conv_pool_0___conv: c = 20, h = 28, w = 28, size = 15680
[INFO 2017-11-28 21:44:35,874 layers.py:2667] output for __conv_pool_0___pool: c = 20, h = 14, w = 14, size = 3920
[INFO 2017-11-28 21:44:35,875 layers.py:2539] output for __conv_pool_1___conv: c = 50, h = 10, w = 10, size = 5000
[INFO 2017-11-28 21:44:35,876 layers.py:2667] output for __conv_pool_1___pool: c = 50, h = 5, w = 5, size = 1250
I1128 21:44:35.881502 14733 MultiGradientMachine.cpp:99] numLogicalDevices=1 numThreads=7 numDevices=8
I1128 21:44:35.928449 14733 GradientMachine.cpp:85] Initing parameters..
I1128 21:44:36.056259 14733 GradientMachine.cpp:92] Init parameters done. Pass 0, Batch 0, Cost 2.302628, {'classification_error_evaluator': 0.9296875}
................................................................................
```
Pass 199, Batch 200, Cost 0.869726, {'classification_error_evaluator': 0.3671875}
...................................................................................................
Pass 199, Batch 300, Cost 0.801396, {'classification_error_evaluator': 0.3046875}
..........................................................................................I1128 23:21:39.443141 14733 MultiGradientMachine.cpp:99] numLogicalDevices=1 numThreads=7 numDevices=8 Test with Pass 199, {'classification_error_evaluator': 0.5248000025749207}
Label of image/dog.png is: 9
我开了7个线程,用了8个Tesla K80 GPU训练,batch_size = 128,迭代次数200次,耗时1h37min,错误分类率为0.5248,这个结果,emm,不算很高,我们可以把它作为一个baseline,后面对其进行调优。
LeNet-5网络结构
Lenet-5网络结构来源于Yan LeCun提出的,原文为《Gradient-based learning applied to document recognition》,论文里使用的是mnist手写数字作为输入数据(32 * 32)进行验证。我们来看一下网络结构。
LeNet-5一共有8层: 1个输入层+3个卷积层(C1、C3、C5)+2个下采样层(S2、S4)+1个全连接层(F6)+1个输出层,每层有多个feature map(自动提取的多组特征)。
Input输入层
cifar10 数据集,每一张图片尺寸:32 * 32
C1 卷积层
- 6个feature_map,卷积核大小 5 * 5 ,feature_map尺寸:28 * 28
- 每个卷积神经元的参数数目:5 * 5 = 25个和一个bias参数
- 连接数目:(5*5+1)* 6 *(28*28) = 122,304
- 参数共享:每个feature_map内共享参数,$\therefore$共(5*5+1)*6 = 156个参数
S2 下采样层(池化层)
- 6个14*14的feature_map,pooling大小 2* 2
- 每个单元与上一层的feature_map中的一个2*2的滑动窗口连接,不重叠,因此S2每个feature_map大小是C1中feature_map大小的1/4
- 连接数:(2*2+1)*1*14*14*6 = 5880个
- 参数共享:每个feature_map内共享参数,有2 * 6 = 12个训练参数
C3 卷积层
这层略微复杂,S2神经元与C3是多对多的关系,比如最简单方式:用S2的所有feature map与C3的所有feature map做全连接(也可以对S2抽样几个feature map出来与C3某个feature map连接),这种全连接方式下:6个S2的feature map使用6个独立的5×5卷积核得到C3中1个feature map(生成每个feature map时对应一个bias),C3中共有16个feature map,所以该层需要学习的参数个数为:(5×5×6+1)×16=2416个,神经元连接数为:2416×8×8=154624个。
S4 下采样层
同S2,如果采用Max Pooling/Mean Pooling,则该层需要学习的参数个数为0个,神经元连接数为:(2×2+1)×16×4×4=1280个。
C5卷积层
类似C3,用S4的所有feature map与C5的所有feature map做全连接,这种全连接方式下:16个S4的feature map使用16个独立的1×1卷积核得到C5中1个feature map(生成每个feature map时对应一个bias),C5中共有120个feature map,所以该层需要学习的参数个数为:(1×1×16+1)×120=2040个,神经元连接数为:2040个。
F6 全连接层
将C5层展开得到4×4×120=1920个节点,并接一个全连接层,考虑bias,该层需要学习的参数和连接个数为:(1920+1)*84=161364个。
输出层
该问题是个10分类问题,所以有10个输出单元,通过softmax做概率归一化,每个分类的输出单元对应84个输入。
LeNet-5的PaddlePaddle实现
1.网络结构 lenet.py
#coding:utf-8
'''
Created by huxiaoman 2017.11.27
lenet.py:LeNet-5
''' import os
from PIL import Image
import numpy as np
import paddle.v2 as paddle
from paddle.trainer_config_helpers import * with_gpu = os.getenv('WITH_GPU', '') != '' def lenet(img):
conv_pool_1 = paddle.networks.simple_img_conv_pool(
input=img,
filter_size=5,
num_filters=6,
num_channel=3,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
conv_pool_2 = paddle.networks.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=16,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
conv_3 = img_conv_layer(
input = conv_pool_2,
filter_size = 1,
num_filters = 120,
stride = 1)
fc = paddle.layer.fc(
input=conv_3, size=84, act=paddle.activation.Sigmoid())
return fc
2.训练代码 train_lenet.py
#coding:utf-8
'''
Created by huxiaoman 2017.11.27
train_lenet.py:训练LeNet-5对cifar10数据集进行分类
''' import sys, os import paddle.v2 as paddle
from lenet import lenet with_gpu = os.getenv('WITH_GPU', '') != '' def main():
datadim = 3 * 32 * 32
classdim = 10 # PaddlePaddle init
paddle.init(use_gpu=with_gpu, trainer_count=7) image = paddle.layer.data(
name="image", type=paddle.data_type.dense_vector(datadim)) # Add neural network config
# option 1. resnet
# net = resnet_cifar10(image, depth=32)
# option 2. vgg
net = lenet(image) out = paddle.layer.fc(
input=net, size=classdim, act=paddle.activation.Softmax()) lbl = paddle.layer.data(
name="label", type=paddle.data_type.integer_value(classdim))
cost = paddle.layer.classification_cost(input=out, label=lbl) # Create parameters
parameters = paddle.parameters.create(cost) # Create optimizer
momentum_optimizer = paddle.optimizer.Momentum(
momentum=0.9,
regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),
learning_rate=0.1 / 128.0,
learning_rate_decay_a=0.1,
learning_rate_decay_b=50000 * 100,
learning_rate_schedule='discexp') # End batch and end pass event handler
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "\nPass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
else:
sys.stdout.write('.')
sys.stdout.flush()
if isinstance(event, paddle.event.EndPass):
# save parameters
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
parameters.to_tar(f) result = trainer.test(
reader=paddle.batch(
paddle.dataset.cifar.test10(), batch_size=128),
feeding={'image': 0,
'label': 1})
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) # Create trainer
trainer = paddle.trainer.SGD(
cost=cost, parameters=parameters, update_equation=momentum_optimizer) # Save the inference topology to protobuf.
inference_topology = paddle.topology.Topology(layers=out)
with open("inference_topology.pkl", 'wb') as f:
inference_topology.serialize_for_inference(f) trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(
paddle.dataset.cifar.train10(), buf_size=50000),
batch_size=128),
num_passes=200,
event_handler=event_handler,
feeding={'image': 0,
'label': 1}) # inference
from PIL import Image
import numpy as np
import os def load_image(file):
im = Image.open(file)
im = im.resize((32, 32), Image.ANTIALIAS)
im = np.array(im).astype(np.float32)
# The storage order of the loaded image is W(widht),
# H(height), C(channel). PaddlePaddle requires
# the CHW order, so transpose them.
im = im.transpose((2, 0, 1)) # CHW
# In the training phase, the channel order of CIFAR
# image is B(Blue), G(green), R(Red). But PIL open
# image in RGB mode. It must swap the channel order.
im = im[(2, 1, 0), :, :] # BGR
im = im.flatten()
im = im / 255.0
return im test_data = []
cur_dir = os.path.dirname(os.path.realpath(__file__))
test_data.append((load_image(cur_dir + '/image/dog.png'), )) # users can remove the comments and change the model name
# with open('params_pass_50.tar', 'r') as f:
# parameters = paddle.parameters.Parameters.from_tar(f) probs = paddle.infer(
output_layer=out, parameters=parameters, input=test_data)
lab = np.argsort(-probs) # probs and lab are the results of one batch data
print "Label of image/dog.png is: %d" % lab[0][0] if __name__ == '__main__':
main()
3.结果输出
I1129 14:52:44.314946 15153 Util.cpp:166] commandline: --use_gpu=True --trainer_count=7
[INFO 2017-11-29 14:52:50,490 layers.py:2539] output for __conv_pool_0___conv: c = 6, h = 28, w = 28, size = 4704
[INFO 2017-11-29 14:52:50,491 layers.py:2667] output for __conv_pool_0___pool: c = 6, h = 14, w = 14, size = 1176
[INFO 2017-11-29 14:52:50,491 layers.py:2539] output for __conv_pool_1___conv: c = 16, h = 10, w = 10, size = 1600
[INFO 2017-11-29 14:52:50,492 layers.py:2667] output for __conv_pool_1___pool: c = 16, h = 5, w = 5, size = 400
[INFO 2017-11-29 14:52:50,493 layers.py:2539] output for __conv_0__: c = 120, h = 5, w = 5, size = 3000
I1129 14:52:50.498749 15153 MultiGradientMachine.cpp:99] numLogicalDevices=1 numThreads=7 numDevices=8
I1129 14:52:50.545882 15153 GradientMachine.cpp:85] Initing parameters..
I1129 14:52:50.651103 15153 GradientMachine.cpp:92] Init parameters done. Pass 0, Batch 0, Cost 2.331898, {'classification_error_evaluator': 0.9609375}
```
......
Pass 199, Batch 300, Cost 0.004373, {'classification_error_evaluator': 0.0}
..........................................................................................I1129 16:17:08.678097 15153 MultiGradientMachine.cpp:99] numLogicalDevices=1 numThreads=7 numDevices=8 Test with Pass 199, {'classification_error_evaluator': 0.39579999446868896}
Label of image/dog.png is: 7
同样是7个线程,8个Tesla K80 GPU,batch_size = 128,迭代次数200次,耗时1h25min,错误分类率为0.3957,相比与simple_cnn的0.5248提高了12.91%。当然,这个结果也并不是很好,如果输出详细的日志,可以看到在训练的过程中loss先降后升,说明有一定程度的过拟合,对于如何防止过拟合,我们在后面会详细讲解。
有一个可视化CNN的网站可以对mnist和cifar10分类的网络结构进行可视化,这是cifar-10 BaseCNN的网络结构:
LeNet-5的Tensorflow实现
tensorflow版本的LeNet-5版本的可以参照models/tutorials/image/cifar10/(https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10)的步骤来训练,不过这里面的代码包含了很多数据处理、权重衰减以及正则化的一些方法防止过拟合。按照官方写的,batch_size=128时在Tesla K40上迭代10w次需要4小时,准确率能达到86%。不过如果不对数据做处理,直接跑的话,效果应该没有这么好。不过可以仔细借鉴cifar10_inputs.py里的distorted_inouts函数对数据预处理增大数据集的思想,以及cifar10.py里对于权重和偏置的衰减设置等。目前迭代到1w次左右,cost是0.98,acc是78.4%
对于未进行数据处理的cifar10我准备也跑一次,看看效果如何,与paddle的结果对比一下。不过得等到周末再补上了 = =
总结
本节用常规的cifar-10数据集做图像分类,用了三种实现方式,第一种是自己设计的一个简单的cnn,第二种是LeNet-5,第三种是Tensorflow实现的LeNet-5,对比速度可以见一下表格:
可以看到LeNet-5相比于原始的simple_cnn在准确率和速度方面都有一定的的提升,等tensorflow版本跑完后可以把结果加上去再对比一下。不过用Lenet-5网络结构后,结果虽然有一定的提升,但是还是不够理想,在日志里看到loss的信息基本可以推断出是过拟合,对于神经网络训练过程中出现的过拟合情况我们应该如何避免,下期我们讲着重讲解。此外在下一节将介绍AlexNet,并对分类做一个实验,对比其效果。
参考文献
1.LeNet-5论文:《Gradient-based learning applied to document recognition》
2.可视化CNN:http://shixialiu.com/publications/cnnvis/demo/
【深度学习系列】用PaddlePaddle和Tensorflow进行图像分类的更多相关文章
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(二)
PaddlePaddle垃圾邮件处理实战(二) 前文回顾 在上篇文章中我们讲了如何用支持向量机对垃圾邮件进行分类,auc为73.3%,本篇讲继续讲如何用PaddlePaddle实现邮件分类,将深度 ...
- 使用腾讯云 GPU 学习深度学习系列之二:Tensorflow 简明原理【转】
转自:https://www.qcloud.com/community/article/598765?fromSource=gwzcw.117333.117333.117333 这是<使用腾讯云 ...
- 【深度学习系列】PaddlePaddle之手写数字识别
上周在搜索关于深度学习分布式运行方式的资料时,无意间搜到了paddlepaddle,发现这个框架的分布式训练方案做的还挺不错的,想跟大家分享一下.不过呢,这块内容太复杂了,所以就简单的介绍一下padd ...
- 【深度学习系列】PaddlePaddle可视化之VisualDL
上篇文章我们讲了如何对模型进行可视化,用的keras手动绘图输出CNN训练的中途结果,本篇文章将讲述如何用PaddlePaddle新开源的VisualDL来进行可视化.在讲VisualDL之前,我们先 ...
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(一)
PaddlePaddle垃圾邮件处理实战(一) 背景介绍 在我们日常生活中,经常会受到各种垃圾邮件,譬如来自商家的广告.打折促销信息.澳门博彩邮件.理财推广信息等,一般来说邮件客户端都会设置一定的 ...
- 【深度学习系列】PaddlePaddle之数据预处理
上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如何读取数据.做数据预处理相关的内容.网上看的 ...
- 【深度学习系列】关于PaddlePaddle的一些避“坑”技巧
最近除了工作以外,业余在参加Paddle的AI比赛,在用Paddle训练的过程中遇到了一些问题,并找到了解决方法,跟大家分享一下: PaddlePaddle的Anaconda的兼容问题 之前我是在服务 ...
- 深度学习系列 Part(3)
这是<GPU学习深度学习>系列文章的第三篇,主要是接着上一讲提到的如何自己构建深度神经网络框架中的功能模块,进一步详细介绍 Tensorflow 中 Keras 工具包提供的几种深度神经网 ...
- 基于TensorFlow的深度学习系列教程 2——常量Constant
前面介绍过了Tensorflow的基本概念,比如如何使用tensorboard查看计算图.本篇则着重介绍和整理下Constant相关的内容. 基于TensorFlow的深度学习系列教程 1--Hell ...
随机推荐
- Prometheus 架构 - 每天5分钟玩转 Docker 容器技术(83)
Prometheus 是一个非常优秀的监控工具.准确的说,应该是监控方案.Prometheus 提供了监控数据搜集.存储.处理.可视化和告警一套完整的解决方案. 让我们先来看看 Prometheus ...
- 斐讯 FIR151M 频繁掉线(OpenWRT解决方案)
0. 现象与前言 在使用斐讯 FIR151M 路由器连接网络时,传输数据时频繁掉线. 官方固件刷了两个版本,问题未解决. 建议高级用户看本教程,要做好不能使用 Web 管理界面的心理准备. 1. 准备 ...
- Httpd2.2常见配置及功能
Httpd 2.2常见配置 要配置http服务的配置文件,先备份一下,养成良好习惯,如果误操作导致http服务起不来,就可以将备份的主配置文件重新覆盖一下 httpd配置文件的组成:有三大部分组成,其 ...
- popup方法
popup方法: 一.创建一个页面 1.创建url和视图函数:: from django.shortcuts import render def p1(request): return render( ...
- ASP.NET MVC @Html.Label的问题
在使用@Html.Lable()来显示Model的某一个字符串属性时,如果该字符串中包含".",那么就会在最终呈现时被截掉开头至"."位置的字符.什么原因尚不清 ...
- Java基础-流程控制(04)
在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.也就是说程序的流程对运行结果有直接的影响.所以,我们必须清楚每条语句的执行流程.而且,很多时候我们要通过控制语句的执行顺序来实现我 ...
- net start mongodb 服务名无效解决方案
net start mongodb 服务名无效 或者 net start mongodb 发生错误5,拒绝访问.是因为没有用管理员权限运行cmd. 解决方案:在window中,在搜索框输入cmd后,在 ...
- angular指令的4种设计模式
指令的功能集非常丰富,不过我们已经发现了指令的帕累托分布:使用angular编写的大量指令只会用到可用性和设计模式中很小的比例,这些指令大概可以分为4类: 只渲染指令--这些指令将渲染作用域中的数据, ...
- C#与lua相互调用
Lua是一种很好的扩展性语言,Lua解释器被设计成一个很容易嵌入到宿主程序的库.LuaInterface则用于实现Lua和CLR的混合编程. (一)C#调用Lua 测试环境:在VS2015中建一个C# ...
- Java语言写出水仙花数,
package com.llh.demo;/** * 水仙花数 * @author llh * */public class Demo14 { public static void main(S ...