TensorFlow 深度学习笔记 卷积神经网络 - 梦里风林 - 博客园

mikel阅读(1205)

来源: TensorFlow 深度学习笔记 卷积神经网络 – 梦里风林 – 博客园

转载请注明作者:梦里风林
Github工程地址:https://github.com/ahangchen/GDLnotes
欢迎star,有问题可以到Issue区讨论
官方教程地址
视频/字幕下载

deep dive into images and convolutional models

Convnet

BackGround

  • 人眼在识别图像时,往往从局部到全局
  • 局部与局部之间联系往往不太紧密
  • 我们不需要神经网络中的每个结点都掌握全局的知识,因此可以从这里减少需要学习的参数数量

Weight share

  • 但这样参数其实还是挺多的,所以有了另一种方法:权值共享

Share Parameters across space

  • 取图片的一小块,在上面做神经网络分析,会得到一些预测
  • 将切片做好的神经网络作用于图片的每个区域,得到一系列输出
  • 可以增加切片个数提取更多特征
  • 在这个过程中,梯度的计算跟之前是一样的

Concept

  • Patch/Kernel:一个局部切片
  • Depth: 数据的深度,图像数据是三维的,长宽和RGB,神经网络的预测输出也属于一维
  • Feature Map:每层Conv网络,因为它们将前一层的feature映射到后一层(Output map)

  • Stride: 移动切片的步长,影响取样的数量
  • 在边缘上的取样影响Conv层的面积,由于移动步长不一定能整除整张图的像素宽度,不越过边缘取样会得到Valid Padding, 越过边缘取样会得到Same Padding
  • Example

  • 用一个3×3的网格在一个28×28的图像上做切片并移动
  • 移动到边缘上的时候,如果不超出边缘,3×3的中心就到不了边界
  • 因此得到的内容就会缺乏边界的一圈像素点,只能得到26×26的结果
  • 而可以越过边界的情况下,就可以让3×3的中心到达边界的像素点
  • 超出部分的矩阵补零就行

Deep Convnet

在Convnet上套Convnet,就可以一层一层综合局部得到的信息

OutPut

将一个deep and narrow的feature层作为输入,传给一个Regular神经网络

Optimization

Pooling

将不同Stride的卷积用某种方式合并起来,节省卷积层的空间复杂度。

  • Max Pooling
    在一个卷积层的输出层上取一个切片,取其中最大值代表这个切片
  • 优点
  • 不增加需要调整的参数
  • 通常比其他方法准确
  • 缺点:更多Hyper Parameter,包括要取最值的切片大小,以及去切片的步长

LENET-5, ALEXNET

  • Average Pooling
    在卷积层输出中,取切片,取平均值代表这个切片

1×1 Convolutions

在一个卷积层的输出层上,加一个1×1的卷积层,这样就形成了一个小型的神经网络。

  • cheap for deeper model
  • 结合Average Pooling食用效果更加

    Inception

    对同一个卷积层输出,执行各种二次计算,将各种结果堆叠到新输出的depth方向上

TensorFlow卷积神经网络实践

数据处理

  • dataset处理成四维的,label仍然作为one-hot encoding
    def reformat(dataset, labels, image_size, num_labels, num_channels):
    dataset = dataset.reshape(
        (-1, image_size, image_size, num_channels)).astype(np.float32)
    labels = (np.arange(num_labels) == labels[:, None]).astype(np.float32)
    return dataset, labels
  • 将lesson2的dnn转为cnn很简单,只要把WX+b改为conv2d(X)+b即可
  • 关键在于conv2d

`conv2d

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)

给定四维的inputfilter tensor,计算一个二维卷积

Args:
  • input: A Tensor. type必须是以下几种类型之一: half, float32, float64.
  • filter: A Tensor. type和input必须相同
  • strides: A list of ints.一维,长度4, 在input上切片采样时,每个方向上的滑窗步长,必须和format指定的维度同阶
  • padding: A string from: "SAME", "VALID". padding 算法的类型
  • use_cudnn_on_gpu: An optional bool. Defaults to True.
  • data_format: An optional string from: "NHWC", "NCHW", 默认为"NHWC"
    指定输入输出数据格式,默认格式为”NHWC”, 数据按这样的顺序存储:
    [batch, in_height, in_width, in_channels]
    也可以用这种方式:”NCHW”, 数据按这样的顺序存储:
    [batch, in_channels, in_height, in_width]
  • name: 操作名,可选.
Returns:

A Tensor. type与input相同

Given an input tensor of shape [batch, in_height, in_width, in_channels]
and a filter / kernel tensor of shape
[filter_height, filter_width, in_channels, out_channels]

conv2d实际上执行了以下操作:

  1. 将filter转为二维矩阵,shape为
    [filter_height * filter_width * in_channels, output_channels].
  2. 从input tensor中提取image patches,每个patch是一个virtual tensor,shape[batch, out_height, out_width, filter_height * filter_width * in_channels].
  3. 将每个filter矩阵和image patch向量相乘

具体来讲,当data_format为NHWC时:

output[b, i, j, k] =
    sum_{di, dj, q} input[b, strides[1] * i + di, strides[2] * j + dj, q] *
                    filter[di, dj, q, k]

input 中的每个patch都作用于filter,每个patch都能获得其他patch对filter的训练
需要满足strides[0] = strides[3] = 1. 大多数水平步长和垂直步长相同的情况下:strides = [1, stride, stride, 1].
– – –

  • 然后再接一个WX+b连Relu连WX+b的全连接神经网络即可

Max Pooling

在tf.nn.conv2d后面接tf.nn.max_pool,将卷积层输出减小,从而减少要调整的参数

max_pool

tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)

Performs the max pooling on the input.

Args:
  • value: A 4-D Tensor with shape [batch, height, width, channels] and
    type tf.float32.
  • ksize: A list of ints that has length >= 4. 要执行取最值的切片在各个维度上的尺寸
  • strides: A list of ints that has length >= 4. 取切片的步长
  • padding: A string, either 'VALID' or 'SAME'. padding算法
  • data_format: A string. ‘NHWC’ and ‘NCHW’ are supported.
  • name: 操作名,可选
Returns:

A Tensor with type tf.float32. The max pooled output tensor.


优化

仿照lesson2,添加learning rate decay 和 drop out,可以将准确率提高到90.6%

参考链接

TensorFlow深度学习笔记 循环神经网络实践 - 梦里风林 - 博客园

mikel阅读(1417)

来源: TensorFlow深度学习笔记 循环神经网络实践 – 梦里风林 – 博客园

转载请注明作者:梦里风林
Github工程地址:https://github.com/ahangchen/GDLnotes
欢迎star,有问题可以到Issue区讨论
官方教程地址
视频/字幕下载

加载数据

  • 使用text8作为训练的文本数据集

text8中只包含27种字符:小写的从a到z,以及空格符。如果把它打出来,读起来就像是去掉了所有标点的wikipedia。

  • 直接调用lesson1中maybe_download下载text8.zip
  • 用zipfile读取zip内容为字符串,并拆分成单词list
  • 用connections模块统计单词数量并找出最常见的单词

达成随机取数据的目标

构造计算单元

embeddings = tf.Variable(
        tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
  • 构造一个vocabulary_size x embedding_size的矩阵,作为embeddings容器,
  • 有vocabulary_size个容量为embedding_size的向量,每个向量代表一个vocabulary,
  • 每个向量的中的分量的值都在-1到1之间随机分布
embed = tf.nn.embedding_lookup(embeddings, train_dataset)
  • 调用tf.nn.embedding_lookup,索引与train_dataset对应的向量,相当于用train_dataset作为一个id,去检索矩阵中与这个id对应的embedding
loss = tf.reduce_mean(
        tf.nn.sampled_softmax_loss(softmax_weights, softmax_biases, embed,
                                   train_labels, num_sampled, vocabulary_size))
  • 采样计算训练损失
optimizer = tf.train.AdagradOptimizer(1.0).minimize(loss)
  • 自适应梯度调节器,调节embedding列表的数据,使得偏差最小
  • 预测,并用cos值计算预测向量与实际数据的夹角作为预测准确度(相似度)指标

传入数据进行训练

  • 切割数据用于训练,其中:
data_index = (data_index + 1) % len(data)
  • 依旧是每次取一部分随机数据传入
  • 等距离截取一小段文本
  • 构造训练集:每个截取窗口的中间位置作为一个train_data
  • 构造标签:每个截取窗口中,除了train_data之外的部分,随机取几个成为一个list,作为label(这里只随机取了一个)
  • 这样就形成了根据目标词汇预测上下文的机制,即Skip-gram
  • 训练100001次,每2000次输出这两千次的平均损失
  • 每10000次计算相似度,并输出与验证集中的词最接近的词汇列表
  • 用tSNE降维呈现词汇接近程度
  • 用matplotlib绘制结果

实现代码见word2vec.py

CBOW

上面训练的是Skip-gram模型,是根据目标词汇预测上下文,而word2vec还有一种方式,CBOW,根据上下文预测目标词汇。

实际上就是将Skip-gram中的输入输出反过来。

  • 修改截取数据的方式
  • 构造标签:每个截取窗口的中间位置作为一个train_label
  • 构造训练集:每个截取窗口中,除了train_label之外的部分,作为train_data(这里只随机取了一个)
  • 这样就形成了根据上下文预测目标词汇的机制,即CBOW
  • 分别从embeding里找到train_data里每个word对应的vector,用tf.reduce_sum将其相加,将相加结果与train_label比较
# Look up embeddings for inputs.
embed = tf.nn.embedding_lookup(embeddings, train_dataset)
# sum up vectors on first dimensions, as context vectors
embed_sum = tf.reduce_sum(embed, 0)
  • 训练中依旧是调节embeding的参数来优化loss
  • 训练结果如下图,可以看到不同单词的接近程度

代码见:
cbow.py

RNN 造句

整体思路是,以一个文本中的一个词作为train data,后续的所有词作为train label,从而能够根据一个给定词,预测后续的片段。

训练数据

  • BatchGenerator
  • text: 全部的文本数据
  • text_size:全部文本的字符串长度
  • batch_size:每段训练数据的大小
  • num_unrollings:要生成的训练数据段的数目
  • segment:整个训练数据集可以分成几个训练数据片段
  • cursor:重要,
  • 一开始记录每个训练数据片段的起始位置坐标,即这个片段位于text的哪个index
  • 执行next_batch生成一个训练数据的时候,游标会从初始位置自增,直到取够batch_size个数据
  • last_batch:上一个训练数据片段
  • 每调用一次next,生成一个num_unrollings长的array,以last_batch开头,跟着num_unrollings个batch
  • 每个batch的作为train_input,每个batch后面的一个batch作为train_label,每个step训练num_unrolling个batch

lstm-cell

  • 为了解决消失的梯度问题,引入lstm-cell,增强model的记忆能力
  • 根据这篇论文设计lstm-cell: http://arxiv.org/pdf/1402.1128v1.pdf
  • 分别有三个门:输入门,遗忘门,输出门,构成一个cell
  • 输入数据是num_nodes个词,可能有vocabulary_size种词
  • 输入门:
  input_gate = sigmoid(i * ix + o * im + ib)
- 给输入乘一个vocabulary_size * num_nodes大小的矩阵,给输出乘一个num_nodes * num_nodes大小的矩阵;
- 用这两个矩阵调节对输入数据的取舍程度
- 用sigmoid这个非线性函数进行激活
  • 遗忘门:
  forget_gate = sigmoid(i * fx + o * fm + fb)

思路同输入门,用以对历史数据做取舍

  • 输出门:
  output_gate = sigmoid(i * ox + o * om + ob)

思路同输入门,用以对输出状态做取舍

  • 组合:
  update = i * cx + o * cm + cb
  state = forget_gate * state + input_gate * tanh(update)
  lstm_cell = output_gate * tanh(state)
- 用同样的方式构造新状态update
- 用遗忘门处理历史状态state
- 用tanh激活新状态update
- 用输入门处理新状态update
- 整合新旧状态,再用tanh激活状态state
- 用输出门处理state

lstm优化

上面的cell中,update,output_gate,forget_gate,input_gate计算方法都是一样的,
可以把四组参数分别合并,一次计算,再分别取出:

values = tf.split(1, gate_count, tf.matmul(i, input_weights) + tf.matmul(o, output_weights) + bias)
input_gate = tf.sigmoid(values[0])
forget_gate = tf.sigmoid(values[1])
update = values[2]

再将lstm-cell的输出扔到一个WX+b中调整作为输出

实现代码见singlew_lstm.py

Optimizer

  • 采用one-hot encoding作为label预测
  • 采用交叉熵计算损失
  • 引入learning rate decay

Flow

  • 填入训练数据到placeholder中
  • 验证集的准确性用logprob来计算,即对可能性取对数
  • 每10次训练随机挑取5个字母作为起始词,进行造句测试
  • 你可能注意到输出的sentence是由sample得到的词组成的,而非选择概率最高的词,这是因为,如果一直取概率最高的词,最后会一直重复这个概率最高的词

实现代码见lstm.py

上面的流程里,每次都是以一个字符作为单位,可以使用多一点的字符做预测,取最高概率的那个,防止特殊情况导致的误判

在这里我们增加字符为2个,形成bigram,代码见:bigram_lstm.py

主要通过BigramBatchGenerator类实现

Embedding look up

由于bigram情况下,vocabulary_size变为 27*27个,使用one-hot encoding 做predict的话会产生非常稀疏的矩阵,浪费算力,计算速度慢

因此引入embedding_lookup,代码见embed_bigram_lstm.py

  • 数据输入:BatchGenerator不再生成one-hot-encoding的向量作为输入,而是直接生成bigram对应的index列表
  • embedding look up调整embedding,使bigram与vector对应起来
  • 将embedding look up的结果喂给lstm cell即可
  • 输出时,需要将label和output都转为One-hot-encoding,才能用交叉熵和softmax计算损失
  • 在tensor里做data到one-hot-encoding转换时,主要依赖tf.gather函数
  • 在对valid数据做转换时,主要依赖one_hot_voc函数

Drop out

  • 在lstm cell中对input和output做drop out
  • Refer to this article

Seq2Seq

  • 最后一个问题是,将一个句子中每个词转为它的逆序字符串,也就是一个seq到seq的转换
  • 正经的实现思路是,word 2 vector 2 lstm 2 vector 2 word
  • 不过tensorflow已经有了这样一个模型来做这件事情:Seq2SeqModel,关于这个模型可以看这个分析
    以及tensorflow的example
  • 只需要从batch中,根据字符串逆序的规律生成target sequence,放到seq2seqmodel里即可,主要依赖rev_id函数
  • 实现见seq2seq.py
  • 注意,用Seq2SeqModel的时候,size和num_layer会在学习到正确的规律前就收敛,我把它调大了一点
def create_model(sess, forward_only):
    model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_size,
                                       target_vocab_size=vocabulary_size,
                                       buckets=[(20, 21)],
                                       size=256,
                                       num_layers=4,
                                       max_gradient_norm=5.0,
                                       batch_size=batch_size,
                                       learning_rate=1.0,
                                       learning_rate_decay_factor=0.9,
                                       use_lstm=True,
                                       forward_only=forward_only)
    return model
  • 参数含义
  • source_vocab_size: size of the source vocabulary.
  • target_vocab_size: size of the target vocabulary.
  • buckets: a list of pairs (I, O), where I specifies maximum input length
    that will be processed in that bucket, and O specifies maximum output
    length. Training instances that have inputs longer than I or outputs
    longer than O will be pushed to the next bucket and padded accordingly.
    We assume that the list is sorted, e.g., [(2, 4), (8, 16)].
  • size: number of units in each layer of the model.
  • num_layers: number of layers in the model.
  • max_gradient_norm: gradients will be clipped to maximally this norm.
  • batch_size: the size of the batches used during training;
    the model construction is independent of batch_size, so it can be
    changed after initialization if this is convenient, e.g., for decoding.
  • learning_rate: learning rate to start with.
  • learning_rate_decay_factor: decay learning rate by this much when needed.
  • use_lstm: if true, we use LSTM cells instead of GRU cells.
  • num_samples: number of samples for sampled softmax.
  • forward_only: if set, we do not construct the backward pass in the model.

参考链接

用Tensorflow让神经网络自动创造音乐 - Charlotte77 - 博客园

mikel阅读(1299)

来源: 用Tensorflow让神经网络自动创造音乐 – Charlotte77 – 博客园

#————————————————————————本文禁止转载,禁止用于各类讲座及ppt中,违者必究————————————————————————#

前几天看到一个有意思的分享,大意是讲如何用Tensorflow教神经网络自动创造音乐。听起来好好玩有木有!作为一个Coldplay死忠粉,第一想法就是自动生成一个类似Coldplay曲风的音乐,于是,开始跟着Github上的教程(项目的名称:Project Magenta)一步一步做,弄了三天,最后的生成的音乐在这里(如果有人能告诉我怎么在博客里插入音乐请赶快联系我!谢谢!)

http://yun.baidu.com/share/link?shareid=1799925478&uk=840708891

http://yun.baidu.com/share/link?shareid=3718079494&uk=840708891

这两段音乐是我生成的十几个音乐中听起来还不错的,虽然还是有点怪,但是至少有节奏,嘿嘿。下面来说一下是怎么做的:

 

1.首先下载Project Magenta

1 git clone https://github.com/tensorflow/magenta.git

 

2.安装需要的工具:

这里(https://www.tensorflow.org/versions/r0.9/get_started/os_setup.html)安装python、bazel和Tensorflow

:我在安装bazel的时候一直出现“Segmentation fault:11”的错误,google了很多解决方法后发现是gcc的安装版本问题,如果你是mac用户,下载了xcode,并不代表你安装了gcc,还必须安装command line tools,如果安装成功,在linux里输入“gcc –version”会出现相应的版本信息,如果没有,就说明安装失败。如果安装失败了,用下载好的bazel再输入“bazel install gcc”,下载完检测一下gcc -v,如果依然是”Segmentation fault:11″错误,恭喜你,遇到和我一样的错误了,google了半天后发现发现了这个:

so,Apple现在已经不用gcc了,改为LLVM。以后要用”clang、clang++”来代替gcc。如果你输入gcc -v,显示”Segmentation fault:11″,不妨输入一下”clang -v”,看一下有没有对应的版本信息。如果有就代表你下载成功了。但是没有完,还有最后一步,把指向gcc的链接改到clang。输入”which gcc”和”which clang”,可以看到你的gcc和clang的位置,然后改一下软链接:

复制代码
1 cd /usr/local/bin
2 sudo mv gcc gcc_OLD
3 sudo ln -s /usr/bin/clang /usr/local/bin/gcc
4 gcc -v
5 Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
6 Target: x86_64-apple-darwin14.5.0
7 Thread model: posix
复制代码

这样就大功告成了!哈哈!不要问我为什么花这么大篇幅写这个看似无关紧要的东西,因为我被这玩意儿折磨了两天!!弄完这个才花了三天结果搞个这个居然就花了两天!!哦,对了,如果你看到这儿不知道bazel是干啥的,简单的说就是一个编译工具,相当于pip的intsall。

现在用bazel来测试一下能不能顺利运行:

1 bazel test //magenta:all

 

 注:如果全部测试成功,很好。如果出现这个错误:

复制代码
 1 INFO: Found 5 targets and 6 test targets...
 2 INFO: Elapsed time: 0.427s, Critical Path: 0.00s
 3 //magenta:basic_one_hot_encoder_test                            (cached) PASSED in 3.7s
 4 //magenta:convert_midi_dir_to_note_sequences_test               (cached) PASSED in 2.3s
 5 //magenta:melodies_lib_test                                     (cached) PASSED in 3.5s
 6 //magenta:midi_io_test                                          (cached) PASSED in 5.5s
 7 //magenta:note_sequence_io_test                                 (cached) PASSED in 3.5s
 8 //magenta:sequence_to_melodies_test                             (cached) PASSED in 40.2s
 9 
10 Executed 0 out of 6 tests: 6 tests pass.
11 There were tests whose specified size is too big. Use the --test_verbose_timeout_warnings command line option to see which ones these are.
复制代码

 

恭喜你,又犯了和我同样的错误:)这个错误是说测试文件太大了,不能一下子全部测试(我16g的内存还不够吗 = =),所以你可以跟我一样手动测试,以其中一个举例:

1 >>>bazel-bin/magenta/basic_one_hot_encoder_test
2 >>>----------------------------------------------------------------------
3 Ran 5 tests in 0.074s
4 
5 OK

把上述六个文件依次测试一下,成功请看下一步。

 

3.创建你的旋律数据集

和机器学习一样,我们得先输入一定的数据让它去训练,这里的训练数据可以自己下载喜欢的音乐,不过Magenta不能直接读取mp3文件,只能读取MIDI文件(mp3太大了,一个10M左右的mp3格式音乐可以转换成100k左右的midi文件)。当然,转换成midi格式的方法很多,我搜集了一个超好用的网址可以在线转:Convert Tool

读取MIDI文件后,Magenta要把MIDI文件转化成Sequence文件才能进行训练

复制代码
##创建旋律数据库
MIDI_DIRECTORY=/Users/shelter/magenta/magenta/music/train #这里换成你的文件路径就行了
SEQUENCES_TFRECORD=/tmp/notesequences.tfrecord

bazel run //magenta:convert_midi_dir_to_note_sequences -- \
--midi_dir=$MIDI_DIRECTORY \
--output_file=$SEQUENCES_TFRECORD \
--recursive
复制代码

 

然后再从这些Sequence序列里提取出旋律:

复制代码
 1 ##从Sequences中提取旋律
 2 SEQUENCES_TFRECORD=/tmp/notesequences.tfrecord
 3 TRAIN_DATA=/tmp/training_melodies.tfrecord #生成的训练文件地址
 4 EVAL_DATA=/tmp/evaluation_melodies.tfrecord 
 5 EVAL_RATIO=0.10 ENCODER=basic_one_hot_encoder
 6 bazel run //magenta/models:basic_rnn_create_dataset -- \
 7 --input=$SEQUENCES_TFRECORD \
 8 --train_output=$TRAIN_DATA \
 9 --eval_output=$EVAL_DATA \
10 --eval_ratio=$EVAL_RATIO \
11 --encoder=$ENCODER
复制代码

ok,这里我们的数据处理就完成了,生成的训练文件在”/tmp/training_melodies.tfrecord”里

 

4.训练神经网络模型

训练数据生成后就可以训练模型了,这里使用的是RNN模型:

复制代码
1 ##训练神经网络模型
2 #首先compile basic_rnn工具
3 bazel build //magenta/models:basic_rnn_train
4 
5 #训练模型,其中“rnn_layer_size”是神经网络的层数,可以自定义
6 ./bazel-bin/magenta/models/basic_rnn_train --experiment_run_dir=/tmp/basic_rnn/run1 --sequence_example_file=$TRAIN_DATA --eval=false --hparams='{"rnn_layer_sizes":[50]}' --num_training_steps=1000
复制代码

 

5.生成测试的旋律

模型那一步非常非常耗时间,Github里设置的是20000次迭代,差点把我的电脑跑烧起来 = =,你可以根据实际硬件情况设置迭代次数。测试旋律和训练的旋律一样,都是midi文件,我这里选取的是Katy Perry的Peacock(小黄歌 = =,想看一下用Coldplay的训练数据在katy Perry上测试的结果是啥)

复制代码
 1 ##生成旋律
 2 #指定测试旋律的文件地址
 3 PRIMER_PATH=/Users/shelter/magenta/magenta/music/coldplay/KatyPerryPeacock.mid #注意这里是绝对地址,只能指定一首歌
 4 bazel run //magenta/models:basic_rnn_generate -- \
 5 --experiment_run_dir=/tmp/basic_rnn/run1 \
 6 --hparams='{"rnn_layer_sizes":[50]}' \
 7 --primer_midi=$PRIMER_PATH \
 8 --output_dir=/tmp/basic_rnn_3 \
 9 --num_steps=64 \
10 --num_outputs=16
复制代码

你可以用 “bazel test //magenta:all”查看结果,在 http://localhost:6006 里查看可视化结果,包含收敛过程,accuracy等。

最后生成的旋律就是开头百度云里的文件了。还有另外一个是用轻音乐测试的,效果也不错。

 

总结:

1.一开始我的训练次数是20000次,到1000次的时候算法发散了,loss值由本来从20几万下降到2000多左右然后突然上升到16000左右,accuracy也下降了,所以就退出了,把迭代次数换成1000次训练。训练结束的时候算法还没有收敛,但是我想快点看到结果,而且电脑跑的太慢了,就直接拿来用了。如果你有GPU或者愿意等个几天跑程序,可以把迭代次数设置的大一点,等算法收敛后再进行测试。模型训练的好坏直接决定最后得到的音乐的好听程度,所以最好等算法收敛后在进行测试。我测试的世界各文件中很多都像乱弹的。

2.这个项目刚开始不久,有一个论坛专门给大家交流学习的心得以及提问题,点这里。上面的注释是我遇到的问题,如果遇到了新的问题,可以在论坛上发帖求助。我看到有的人生成的音乐很有那种诡异的哥特风哈哈。

3.这个项目背后的具体原理我没有写,Github上写的很清楚,可以参考这里

4.生成后的音乐可以根据自己的需要加上节拍,应该会好听一点~

 

总之,现在开始做吧,很有趣的!~

作者:Charlotte77

出处:http://www.cnblogs.com/charlotte77/

本文以学习、研究和分享为主,如需转载,请联系本人,标明作者和出处,非商业用途!

VS低版本打开高版本解决方案(如08打开10、12、13版本vs编译的项目) - Demona - 博客园

mikel阅读(1799)

来源: VS低版本打开高版本解决方案(如08打开10、12、13版本vs编译的项目) – Demona – 博客园

一、vs2005打开vs2008编译的项目:
1、用记事本打开sln文件,将:
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
改成:
Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005

2、用记事本打开csproj文件,将:
<Project ToolsVersion=”3.5″ DefaultTargets=”Build”
中的ToolsVersion=”3.5″删除
然后将:
<Import Project=”$(MSBuildToolsPath)
改为:
<Import Project=”$(MSBuildBinPath)
二、vs2010打开vs2012或vs2013编译的项目:
1、-1用记事本打开sln文件,将:
Microsoft Visual Studio Solution File, Format Version 12.00# Visual Studio 2012
改成:
Microsoft Visual Studio Solution File, Format Version 11.00# Visual Studio 2010
-2

VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
修改为
VisualStudioVersion = 10.0.40219.1
MinimumVisualStudioVersion = 10.0.40219.1
2、用记事本打开csproj文件,将:
<Project ToolsVersion=”3.5″ DefaultTargets=”Build”
中的ToolsVersion=”3.5″删除
然后将:
<Import Project=”$(MSBuildToolsPath)
改为:
<Import Project=”$(MSBuildBinPath)
有的时候csproj这个文件不用管,直接改sln文件vs版本即可,但是有的时候都改了还是不行的话最简单的办法就是:
新建一个解决方案,把里面的类从新加载一遍。

C#中数组与ArrayList的简单使用 - Rain Man - 博客园

mikel阅读(1262)

来源: C#中数组与ArrayList的简单使用 – Rain Man – 博客园

1. 多维数组

多维数组:行数和列数在定义时已确定

string[,] arr = new string[2, 3];
arr[0, 0] = "坐标:1-1";
arr[0, 1] = "坐标:1-2";
arr[0, 2] = "坐标:1-3";
arr[1, 0] = "坐标:2-1";
arr[1, 1] = "坐标:2-2";
arr[1, 2] = "坐标:2-3";

int m = arr.GetLength(0);    // 行数
int n = arr.GetLength(1);    // 列数

StringBuilder str = new StringBuilder();    // 类似于String类型变量

for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        str.Append(arr[i, j] + "  ");
    }
}
Console.WriteLine(str.ToString());

2. 锯齿数组

锯齿数组:行数确定,列数不定,数组里套数组。

int[][] arr = new int[3][];
arr[0] = new int[] { 1, 2, 3, 4 };
arr[1] = new int[] { 100 };
arr[2] = new int[] { 10, 20, 30 };

foreach (int[] i in arr) {
    foreach (int x in i) {
        Console.WriteLine(x);   // 遍历所有元素
    }
}
Console.WriteLine(arr[2][2]);   // 访问单一元素,注意和多维素组的差别,会输出30

3. 数组的常用操作

  • arr.Length属性:数组中元素个数;
  • arr.GetLength(n):获取数组arr第n维元素的个数;
  • arr.Rank:获取数组arr的维数;

①克隆数组,就是复制一份新的

int[] arr = new int[] { 1, 2, 3, 4};
int[] arr2 = (int[])arr.Clone();
for (int i = 0; i < arr2.Length; i++)
{
    Console.Write(arr2[i]);
}

4. ArrayList

命名空间:using System.Collections;

ArrayList就是传说中的动态数组,它可以动态的增加和减少元素,另外它可以存储任意个数和任意类型的元素。

属性

  • Count属性是目前ArrayList包含的元素的数量,这个属性是只读的
  • Capacity属性是目前ArrayList能够包含的最大数量,可以手动的设置这个属性,但是当设置为小于Count值的时候会引发一个异常。

方法

  • Add(v):添加一个元素(参数v,任何类型)到当前列表的末尾
  • AddRange方法用于添加一批元素到当前列表的末尾
  • Remove方法用于删除一个元素,通过元素本身的引用来删除
  • RemoveAt(i):方法用于删除一个元素,通过索引值(参数i)来删除
  • RemoveRange用于删除一批元素,通过指定开始的索引和删除的数量来删除
  • Insert用于添加一个元素到指定位置,列表后面的元素依次往后移动
  • InsertRange用于从指定位置开始添加一批元素,列表后面的元素依次往后移动
  • Clear():方法用于清除现有所有的元素
  • Contains(v):方法用来查找某个对象(参数v)在不在列表之中
  • ToArray(Type t):这个方法把ArrayList的元素Copy到一个新的数组中,参数为Type类型

① 简单使用

ArrayList list = new ArrayList();  // 声明变量

list.Add(2);
list.Add("北京");
list.Add(new int[] { 2, 4, 6 });
list.Add("南京");
list.Add(3);

foreach (var p in list) {
    Console.Write(p.ToString() + " | ");  // 输出:2 | 北京 | System.Int32[] | 南京 | 3 |
}

② ArrayList与数组转换

元素类型相同的转换

ArrayList list = new ArrayList();
list.Add(1);
list.Add(2);
list.Add(3);

// 转换方式1:ToArray()
int[] list2 = (int[])list.ToArray(typeof(int));

// 转换方式2:CopyTo()
int[] list3 = new int[list.Count];
list.CopyTo(list3);

元素类型不同的转换

ArrayList arr = new ArrayList(){ "北京", 1};   //往数组中添加不同类型的元素

object[] arr2 = (object[])arr.ToArray(typeof(object)); //正确
string[] arr3 = (string[])arr.ToArray(typeof(string)); //错误

提升iOS审核通过率之“IPv6兼容测试” - 腾讯WeTest - 博客园

mikel阅读(1432)

来源: 提升iOS审核通过率之“IPv6兼容测试” – 腾讯WeTest – 博客园

一、背景

在WWDC2015大会上苹果宣布iOS9将支持纯IPv6的网络服务。2016年6月1号,所有提交到AppStore上的应用都必须支持IPv6,否则将通不过审核。为了确保我们的app正常提交到苹果进行审核,不耽误项目进度,我们必须在提交到AppStore前对待提交app做IPv6兼容测试。

 

二、为什么要使用IPv6

从IPv4到IPv6,IP地址的数量从2的32次方扩展到2的128次方,这个是IPv6碾压IPv4的地方,足够地球上的每粒沙子分配一个或者多个IP地址。当然,除了IPv4本身的原因(地址枯竭)外,下面提到的几点,也说明了IPv6比IPv4更加高效,例如:

1. 避免了网络地址转换(NAT)的需要

2. 通过使用简化的头提供了更快的路由通过网络

3. 防止网络碎片

4. 避免广播邻居地址解析

但最根本的原因莫过于苹果审核的压力,不得不将app适配IPv6。

 

三、测试应该关注的点

1. 保证项目代码中使用的是更高层次的网络API,避免使用socket API

在苹果官网上,有这么一张图,说明了哪些框架支持IPv6,哪些不支持, 如下图所示:

 

图中蓝色部分默认支持IPv6,如果项目代码中使用的是WebKit或者AFNetWorking这些网络框架,那么需要改动的代码不会很多。

 

2. 走读代码中是否使用了IP地址

比如,检查代码中是否包含了类似于192.168.0.1的地址,如果有,需要换为其对应的域名地址。

 

3. 检查代码是否包含只适用于IPv4的API

确保项目代码中没有以下API:

inet_addr()

inet_aton()

inet_lnaof()

inet_makeaddr()

inet_netof()

inet_network()

inet_ntoa()

inet_ntoa_r()

bindresvport()

getipv4sourcefilter()

setipv4sourcefilter()

如果有了这些代码也不要怕,只需要将对应的API换为IPv6所支持的API即可,下表是IPv4和IPv6的对应关系表:

 

 

4. 本地搭建IPv6环境,回归app中所有网络请求的模块

我们现在连的网络,无论是wifi还是移动、联通、电信的各种不同类型的网络,都是IPv4的,也就是实际生活中和我们打交道的全是IPv4环境。但我们需要测试IPv6在iOS APP中的兼容性,是必然不能在现有的网络环境下测试的,需要测试人员自己构建测试环境。

值得欣慰的是,苹果公司在MAC OS X 10.11以后的系统中就埋下了开启IPv6环境的彩蛋。我们所要做的就是参照官方文档,在本地搭建这样的测试环境。即用MAC机建立一个热点,然后用iPhone连接该热点,回归待测模块。简单的示意图如下所示:

 

 

a)需要准备的设备:

系统是OS X 10.11以后版本的MAC机(该MAC机要使用非WIFI方式上网,且支持双网卡)一台,iPhone手机一台。这个地方有些土豪同学可能会有疑问,我的Mac本本只有无线网卡,没有有线网孔啊,这时你就需要一个苹果Thunderbolt千兆以太网转换器,如下图所示:

 

 

b)开启NAT64网络

打开“系统偏好设置”,按住option键的同时点击“共享”,如下图所示:

 

之后,在共享页就能看到“创建NAT64网络”的可选框了,如下图所示:

 

到此,我们的NAT64网络就创建完成了,剩下的就是创建热点了。

 

c)在MAC机上创建WiFi热点

MAC上创建WiFi热点,很简单,这里就不再详细说明了,看下面这张图也能知道怎么创建的:

 

这里想说明一点,也是比较关键的一点:点击上图中的“启动”button后,有些网络会出现如下图所示的共享失败情况:

 

出现这种情况,说明你当前使用的网络受到了802.1x协议的限制,无法共享。只能通过去802.1x保护或者换其他可以共享的网络进行共享。

如果是在公司网络,可以寻求IT进行网络切换,或者找开发他们搭建好的网络环境进行测试(ps:如果开发没有IPv6网络环境,且发版时间又比较紧张的情况下,可以和老大申请回家办公,或者随便一个咖啡馆)。

如果热点搭建成功,是可以在mac机右上角的网络状态处看到如下图所示的图标:

 

d)iPhone手机连接创建好的热点,连接成功后,可以查下iPhone手机的IP地址。

我这里看到的是169.254.*.*的IP地址,这类地址属于保留地址,具体什么是保留地址,请自行百度。

 

e)回归待测app,确保所有的网络请求在本地搭建好的IPv6网络环境下正常的,这里说的“正常”就是指和在IPv4网络环境下的表现一致。

经测试,在IPv6网络环境下,QQ可以正常使用,微信网络连接失败(微信最新的SDK已支持IPv6)。

最后,如果感兴趣的话,可以用抓包工具看一下,IPv6的地址长什么样子,之前都是在课本中见到过,这次测试也算是头一次真正看到IPv6的地址啦,如下图所示:

 

 

四、项目实战

这里以地图SDK为例,简单说明具体的测试步骤。地图SDK,是地图软件开发工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合,它包括接口API、示例代码、支持性的技术注解或者其他的支持文档,有了这些,第三方开发者可以很方便的使用这些API开发自己所需的功能,这里附上腾讯地图开放平台地址:http://lbs.qq.com/ios_v1/index.html。

目前和腾讯地图SDK合作的小伙伴有京东、58同城、携程、嘀嘀等。

1. 首先需要分析该SDK使用到了哪些库

地图SDK中使用的framework如下图:

 

针对地图SDK中使用到的这些库,逐一确认是否支持IPv6,比如WebKit.framework框架,苹果官方文档就明确说明了支持IPv6。

 

2. 检查SDK源码中是否使用到了IP地址

这块主要是走查所有有网络请求的模块,是否将IP地址替换为了域名,这块一般是在代码某个配置文件中进行定义的。比如,下图所示的server地址:

 

 

3. 检查代码中是否包含只支持IPv4的API

由于SDK使用的是WebKit.framework,不存在底层的socket API,这块没有风险。

 

4. 本地搭建测试环境,回归SDK网络相关模块

这里,需要保证在IPv6环境下的功能与在IPv4环境下表现一致的,就可以说明测试通过。

 

5. 抓包确认

最后,可以通过抓包看看是否真的走的是所搭建的IPv6的测试环境,当然也可以亲眼目睹下IPv6的IP地址长什么样儿。


【腾讯WeTest iOS预审工具】

为了提高IEG苹果审核通过率,腾讯专门成立了苹果审核测试团队,打造出iOS预审工具这款产品。经过1年半的内部运营,腾讯内部应用的iOS审核通过率从平均35%提升到90%+。

现将腾讯内部产品的过审经验,以线上工具的形式共享给各位。在WeTest腾讯质量开放平台上可以在线使用。点击链接:http://wetest.qq.com/product/ios 即可立即体验!


iOS预审工具分四步进行预审服务

【一键扫描】只需提供ipa包、审核图片、审核视频、应用描述,即可在4小时内拿到一份完整的检测报告,定位问题的同时提供解决方案,助您成功通过审核。

【案例分享】集结iOS审核失败常见原因,丰富案例为您提供参考依据。

【专家服务】腾讯专家团队为您分析各种疑难杂症,提出最优解决方案。

【ASO优化】专业优化AppStore内关键字搜索结果,让产品离用户更近一步。希望App/手游在预审验收保证下,都可以快快乐乐过审,开开心心赚钱。


关注“腾讯WeTest”公众号,获取更多干货:

ecshop 重置后台密码 MD5+salt - ChrisZZ - 博客园

mikel阅读(943)

来源: ecshop 重置后台密码 MD5+salt – ChrisZZ – 博客园

ecshop密码加密方式:
MD5 32位+salt,简单来说就是明文密码用MD5加密一次,然后在得到的MD5字符后边加上salt字段值(salt值为系统随机生成,生成以后不再改变)再进行一次MD5加密,得到的结果插入password字段。
所以如果忘记后台密码需要重置的话要进行以下操作:
以把密码重置成admin123为例
一、用MD5加密工具对“admin123”进行32位加密得到“0192023a7bbd73250516f069df18b500”;
二、用phpmyadmin打开数据库ecs_admin_user表,查看后台用户salt字段的值例如“3996”;
三、对字符串“0192023a7bbd73250516f069df18b5003996”再次进行MD532位加密得到“ff7c0d9076ca4dcc6a88e6141a18a2d6”;
四、将第三步得到的字符串填写到ecs_admin_user表后台用户的passowrd字段,然后执行。
经过以上四步,后台密码就重置成admin123了。

reference: http://jm.ncxyol.com/post-104.html

[原创]DESTOON二次开发笔记(3)

mikel阅读(2099)

  • 后台会员添加取消email的有效校验
  • 后台模块调取E:\UPUPW\htdocs\b2b\admin1.php
  • 然后调取(include MD_ROOT.’/admin/’.$file.’.inc.php’) or msg();
  • 然后调取E:\UPUPW\htdocs\b2b\admin\index.inc.php
  • 然后调取E:\UPUPW\htdocs\b2b\module\member\admin\index.inc.php处理
  • 然后调取E:\UPUPW\htdocs\b2b\module\member\admin\template\member_add.tpl.php模板修改了email校验的js代码
  • 然后修改E:\UPUPW\htdocs\b2b\module\member\admin\index.inc.php中的
  • 首页公司搜索功能:
  1. 首页搜索调取Company/search.php中,以参数形式提交参数kw为关键词,ptype为用户类型
  2. 调取E:\UPUPW\htdocs\b2b\module\company\search.inc.php中的if($DT_QST) {}的查询
  3. 关键词高亮显示调取了E:\UPUPW\htdocs\b2b\include\module.func.php中的function highlight($str) {}高亮显示
  4. 地区异步选择的控件:$area_select = ajax_area_select(‘areaid’, $L[‘all_area’], $areaid);函数ajax_area_select()在E:\UPUPW\htdocs\b2b\include\post.func.php中
  • 系统常量和变量:
常量 说明 备注
DT_ROOT 站点物理路径
DT_PATH 站点首页网址
DT_SKIN 风格目录网址
DT_STATIC 静态文件地址 >=5.0
DT_ADMIN 是否在管理后台
DT_DOMAIN Cookie作用域
DT_LANG 站点语言
DT_KEY 安全密钥
DT_CHARSET 字符编码
DT_CACHE 缓存目录物理路径
DT_VERSION 系统版本
DT_RELEASE 更新时间
VIP VIP名称
变量 说明 备注
$DT_TIME 当前时间 Unix时间戳
$DT_IP 当前IP
$DT_URL 当前网址URL
$DT_PRE 数据表前缀
$db 数据库操作对象
$dc 缓存操作对象
$DT 网站设置 数组
$EXT 扩展功能模块设置 数组
$MOD 当前模块设置 数组,仅模块内部存在
$MODULE 系统模块信息 数组
$forward 来源页面
$page 当前页码
$moduleid 模块ID
$catid 分类ID
$CAT $catid所有属性 数组
$areaid 地区ID
$ARE $areaid所有属性 数组
$itemid 信息ID
$cityid 分站ID
$kw 搜索关键词
$_userid 当前登录会员的会员ID 0为游客
$_username 当前登录会员的会员名
$_truename 当前登录会员的姓名
$_company 当前登录会员的公司名
$_money 当前登录会员的资金
$_credit 当前登录会员的积分
$_sms 当前登录会员的短信
$_message 当前登录会员的站内信
$_chat 当前登录会员的新对话
$_groupid 当前登录会员的会员组
$MG 当前登录会员的会员组权限

 

原因是由于在系统设置-网站设置—SEO优化里面的–公司主页绑定二级域名中设置了www.xxx.com的原因导致userurl根据这个条件进行二级域名解析导致所有url格式为http://www.xxx.com/username/这种格式,删除掉这里的域名就好了

  • 二次开发入门:

一、初始化系统

包含系统根目录下的common.inc.php即可初始化系统。

例如在站点根目录下创建一个hello.php。

示例代码:

<?php
require ‘common.inc.php’;
echo ‘Hello World’;
?>
二、编写逻辑

系统初始化之后,就可以在php文件里编写逻辑代码,同时也可以调用系统内置的变量、函数和类了。

示例代码:

<?php
require ‘common.inc.php’;

echo DT_ROOT;//输出站点的物理路径
echo ‘<br/>’;

echo DT_PATH;//输出站点的首页地址
echo ‘<br/>’;

$r = $db->get_one(“selec * from {$DT_PRE}category”);//从分类表里查询一条数据
print_r($r);//打印读取的数据

$A = cache_read(‘area.php’);//读取系统的地区缓存
print_r($A);//打印读取的数据

print_r($MODULE);//打印系统模块数据

message(‘Hello World’);//输出一段提示信息
?>
三、应用模板

所有输出给浏览器的HTML均通过模板里的规则显示。

使用方法:

include template(‘a’, ‘b’);
参数a表示模版名称
参数b表示模板存放的目录,此参数可以不设置

假如模板目录为default,那么:
template(‘a’, ‘b’); 代表 template/default/b/a.htm 模板文件
template(‘a’); 代表 template/default/a.htm 模板文件

示例代码:

<?php
require ‘common.inc.php’;
template(‘hello’);
?>
template/default/hello.htm 模板文件需要提前创建

[原创]DESTOON二次开发笔记(2)

mikel阅读(4119)

  • 文章列表页面调取数据:

1、文章模型的栏目,页面目录在:E:\UPUPW\htdocs\b2b\template\default\article\list.htm

List.htm模板调取文章列表中

<div class=”catlist”>

{if $tags}{template ‘list-cat’, ‘tag’}{/if}

</div>

其中的{template ‘list-cat’}的模板路径在:

E:\UPUPW\htdocs\b2b\template\05zsdt12\tag\list-cat.htm

要修改就修改里面的文章列表样式

2、修改分页的样式在:E:\UPUPW\htdocs\b2b\api\pages.default.php

后台的网站设置的

  • 产品展示的页面调取数据:
  1. 产品展示页面的产品列表调取方法:

<!–{tag(“moduleid=$moduleid&condition=status=3$dtype&areaid=$cityid&catid=$catid&pagesize=”.$MOD[pagesize].”&page=$page&showpage=1&datetype=5&order=”.$MOD[order].”&fields=”.$MOD[fields].”&lazy=$lazy&template=list-sell”)}–>

  1. 产品展示列表的模板,这里template的list-sell,在

E:\UPUPW\htdocs\b2b\template\05zsdt12\tag\list-sell.htm

注意:destoon自定义的重复使用的模块都在template\05zsdt12\tag\中定义了

  • 模板页面获取url中的参数:

Destoon自动将url中提交的参数封装成$参数名的变量,传递给模板

模板调取的时候直接用$参数名调取即可

 

  • 后台的用户编辑界面的“公司类型”增加“专业机构”

修改E:\UPUPW\htdocs\b2b\template\05zsdt12\member\edit.htm中的

{dselect($COM_TYPE, ‘post[type]’, ‘请选择’, $type, ‘id=”type”‘, 0)}

dselect()函数在E:\UPUPW\htdocs\b2b\include\post.func.php中定义调取的$MOD[‘com_type’]这些设置在

 

  • 后台管理系统的模块查询流程

Admin1.php 进行登录校验   à 按照moduleid查询对应模块  à

 

根据acton调取对应moduleid的目录下的aciton.php文件  à 调取对应module目录下的action.htm的文件

 

 

  • destoon dsubstr截取如何过滤html标记

{dsubstr(strip_tags($t[title]),20,’…’)}

 

  • 模板调用Tag函数的定义:

文件路径:E:\UPUPW\htdocs\b2b\include\tag.func.php

函数声明:

 

  • 订单交易表
  • 操作表:destoon_mall_order
  • 订单状态:status 0=未确认1=已确认 2=已付款 未发货 3=已发货 4=成功
  • 自定义destoon的头部destoon_member

destoon原有的js和css不要删除,只需要修改line.htm就ok了

调用chip/line.htm

  • DESTOON的页面常量的定义在:

语言包路径:E:\UPUPW\htdocs\b2b\lang\zh-cn\

 

  • 专家智库的详情页面:

E:\UPUPW\htdocs\b2b\template\05zsdt12\company\show.htm的页面默认是个人会员的模板

 

 

 

  • 公司网站首页解析过程:
  • 首先调取:E:\UPUPW\htdocs\b2b\index.php
  • 然后调取:

$moduleid = 4;

$module = ‘company’;

$MOD = cache_read(‘module-‘.$moduleid.’.php’);

include load(‘company.lang’);

require DT_ROOT.’/module/’.$module.’/common.inc.php’;

include DT_ROOT.’/module/’.$module.’/init.inc.php’;

 

  • 然后调取:E:\UPUPW\htdocs\b2b\module\company\init.inc.php中调取
  • 然后调取:E:\UPUPW\htdocs\b2b\module\company\homepage.inc.php中的

$r = $db->get_one(“SELECT content FROM {$content_table} WHERE userid=$userid”);

获取destoon_company_data中的数据

  • 页面调取的样式在:E:\UPUPW\htdocs\b2b\company\skin

通用样式在common.css

  • 首页的菜单调取缓存中的E:\UPUPW\htdocs\b2b\module\company\init.inc.php中的

$HOME = get_company_setting($COM[‘userid’], ”, ‘CACHE’);

然后获取$menu_order = explode(‘,’, isset($HOME[‘menu_order’]) ? $HOME[‘menu_order’] : $_menu_order);

然后遍历调取E:\UPUPW\htdocs\b2b\include\global.func.php中的userurl();函数生成首页导航的$MENU的链接;修改sell页面的增加了&view=1的参数

  • 订单购买的流程:

备注:每个模块的配置信息都存储到E:\UPUPW\htdocs\b2b\file\cache\module-模型ID.php中

订单不需要卖家确认的设置在

 

  • 产品展示和交易中心的缩略图不显示问题:
  1. 由于htm中的延迟加载的图片:

{if $lazy}$(function(){$(“img”).lazyload();});{/if}

  1. $lazy的值在E:\UPUPW\htdocs\b2b\common.inc.php中设置的

$lazy = $DT[‘lazy’] ? 1 : 0;

$DT是从$CACHE = cache_read(‘module.php’);中获取到

Module.php是网站设置的页面中设置的缓存到E:\UPUPW\htdocs\b2b\file\cache\module.php中

修改图片延迟加载的设置在