精华内容
下载资源
问答
  • fastText原理和文本分类实战,看这篇就够了

    万次阅读 多人点赞 2019-03-19 11:19:48
    fastText是个快速文本分类算法,与基于神经网络的分类算法相比有两大优点: 1、fastText在保持高精度的情况下加快了训练速度测试速度 2、fastText不需要预训练好的词向量,fastText会自己训练词向量 3、fastText...

    前言:若需获取本文全部的手书版原稿资料,扫码关注公众号,回复: FastText 即可获取。

    原创不易,转载请告知并注明出处!扫码关注公众号【机器学习与自然语言处理】,定期发布知识图谱,自然语言处理、机器学习等知识,添加微信号【17611428102】进讨论群,加好友时备注来自CSDN。
    机器学习与自然语言处理

    Word2vec, Fasttext, Glove, Elmo, Bert, Flair pre-train Word Embedding源码+数据Github网址: 词向量预训练实现Github


    fastText原理篇

    一、fastText简介

    fastText是一个快速文本分类算法,与基于神经网络的分类算法相比有两大优点:
    1、fastText在保持高精度的情况下加快了训练速度和测试速度
    2、fastText不需要预训练好的词向量,fastText会自己训练词向量
    3、fastText两个重要的优化:Hierarchical Softmax、N-gram

    二、fastText模型架构

    fastText模型架构和word2vec中的CBOW很相似, 不同之处是fastText预测标签而CBOW预测的是中间词,即模型架构类似但是模型的任务不同。下面我们先看一下CBOW的架构:
    在这里插入图片描述
    word2vec将上下文关系转化为多分类任务,进而训练逻辑回归模型,这里的类别数量|V|词库大小。通常的文本数据中,词库少则数万,多则百万,在训练中直接训练多分类逻辑回归并不现实。word2vec中提供了两种针对大规模多分类问题的优化手段, negative sampling 和hierarchical softmax。在优化中,negative sampling 只更新少量负面类,从而减轻了计算量。hierarchical softmax 将词库表示成前缀树,从树根到叶子的路径可以表示为一系列二分类器,一次多分类计算的复杂度从|V|降低到了树的高度

    fastText模型架构:其中x1,x2,…,xN−1,xN表示一个文本中的n-gram向量,每个特征是词向量的平均值。这和前文中提到的cbow相似,cbow用上下文去预测中心词,而此处用全部的n-gram去预测指定类别
    在这里插入图片描述

    三、层次softmax

    softmax函数常在神经网络输出层充当激活函数,目的就是将输出层的值归一化到0-1区间,将神经元输出构造成概率分布,主要就是起到将神经元输出值进行归一化的作用,下图展示了softmax函数对于输出值z1=3,z2=1,z3=-3的归一化映射过程
    在这里插入图片描述
    在标准的softmax中,计算一个类别的softmax概率时,我们需要对所有类别概率做归一化,在这类别很大情况下非常耗时,因此提出了分层softmax(Hierarchical Softmax),思想是根据类别的频率构造霍夫曼树来代替标准softmax,通过分层softmax可以将复杂度从N降低到logN,下图给出分层softmax示例:
    在这里插入图片描述
    在层次softmax模型中,叶子结点的词没有直接输出的向量,而非叶子节点都有响应的输在在模型的训练过程中,通过Huffman编码,构造了一颗庞大的Huffman树,同时会给非叶子结点赋予向量。我们要计算的是目标词w的概率,这个概率的具体含义,是指从root结点开始随机走,走到目标词w的概率。因此在途中路过非叶子结点(包括root)时,需要分别知道往左走和往右走的概率。例如到达非叶子节点n的时候往左边走和往右边走的概率分别是:
    在这里插入图片描述
    以上图中目标词为w2为例,
    在这里插入图片描述
    到这里可以看出目标词为w的概率可以表示为:
    在这里插入图片描述
    其中θn(w,j)是非叶子结点n(w,j)的向量表示(即输出向量);h是隐藏层的输出值,从输入词的向量中计算得来;sign(x,j)是一个特殊函数定义
    在这里插入图片描述
    此外,所有词的概率和为1,即
    在这里插入图片描述
    最终得到参数更新公式为:
    在这里插入图片描述

    四、N-gram特征

    n-gram是基于语言模型的算法,基本思想是将文本内容按照子节顺序进行大小为N的窗口滑动操作,最终形成窗口为N的字节片段序列。而且需要额外注意一点是n-gram可以根据粒度不同有不同的含义,有字粒度的n-gram和词粒度的n-gram,下面分别给出了字粒度和词粒度的例子:
    在这里插入图片描述
    在这里插入图片描述
    对于文本句子的n-gram来说,如上面所说可以是字粒度或者是词粒度,同时n-gram也可以在字符级别工作,例如对单个单词matter来说,假设采用3-gram特征,那么matter可以表示成图中五个3-gram特征,这五个特征都有各自的词向量,五个特征的词向量和即为matter这个词的向其中“<”和“>”是作为边界符号被添加,来将一个单词的ngrams与单词本身区分开来:
    在这里插入图片描述
    从上面来看,使用n-gram有如下优点
    1、为罕见的单词生成更好的单词向量:根据上面的字符级别的n-gram来说,即是这个单词出现的次数很少,但是组成单词的字符和其他单词有共享的部分,因此这一点可以优化生成的单词向量
    2、在词汇单词中,即使单词没有出现在训练语料库中,仍然可以从字符级n-gram中构造单词的词向量
    3、n-gram可以让模型学习到局部单词顺序的部分信息, 如果不考虑n-gram则便是取每个单词,这样无法考虑到词序所包含的信息,即也可理解为上下文信息,因此通过n-gram的方式关联相邻的几个词,这样会让模型在训练的时候保持词序信息

    但正如上面提到过,随着语料库的增加,内存需求也会不断增加,严重影响模型构建速度,针对这个有以下几种解决方案:
    1、过滤掉出现次数少的单词
    2、使用hash存储
    3、由采用字粒度变化为采用词粒度



    fastText实战篇

    fastText实战篇来自对fastText官方文档的翻译,官网网址为:fasttext学习官网,英文阅读能力好的强烈建议直接读原文,下面翻译可以提供给不想读英文文档的读者,翻译能力有限,有错请指正!

    一、Fasttext介绍

    1、什么是fastText

    fastText是一个高效学习单词表示和句子分类

    2、fastText环境要求

    fastText需要运行在Mac OS或Linux上,因为fastText使用了C++11,因此需要很好支持C++11的编译器,支持的编译器包括:
    (1) gcc-4.6.3 或者更新版本
    (2) clang-3.3 或者更新版本

    编译是使用Makefile执行的,因此你需要有一个工作的make,对于单词相似度评估脚本则需要如下环境:
    (1) python2.6 或者更新
    (2) numpy 和 spicy

    3、在本地快速搭建fastText

    为了搭建fastText,打开命令窗口依次执行以下命令:

    $ git clone https://github.com/facebookresearch/fastText.git
    $ cd fastText
    $ make
    

    上述命令将为所有类和主二进制fastText生成目标文件,如果你不打算使用默认的系统范围编译器,可以更新Makefile(CC和include)开头定义的两个宏

    二、fastText教程-文本分类

    文本分类对许多应用来说都是一个核心问题,例如:垃圾邮件分类、情感分析以及智能问答等。在此教程中,详细阐述通过fastText如何搭建一个文本分类模型

    1、什么是文本分类

    文本分类的目的是将文档(例如电子邮件、帖子、文本消息,产品评论等)分给一个或多个类别,表示这些类别可以是评价分数,垃圾邮件、非垃圾邮件,或者是文档所用的语言。目前,构建此类分类器最主要的方法是机器学习,机器学习方法从实例中学习分类规则,为了构建分类器,我们需要带标签的数据,标签数据指的数据包括文档和此文档所对应的类别(或称标记或标签),例如,我们可以构建一个分类器,该分类器将cooking自动分为几个标签如:pot、bowl、baking

    2、安装fastText

    首先我们需要做的便是安装搭建fastText,需要系统支持c++ 11的c++编译器,先从GitHub上下载fastText到本地(版本在更新,可以到GitHub上查看最近版本进行下载):

    $ wget https://github.com/facebookresearch/fastText/archive/v0.1.0.zip
    

    然后将下载的zip文件夹进行解压,解压后进入目录对fastText项目执行make命令进行编译(因此这里便需要你的系统有支持c++11的编译器)

    $ unzip v0.1.0.zip
    $ cd fastText-0.1.0
    $ make
    

    在根目录下运行名为fasttext的二进制文件,便会打印出fastText支持的各种不同的命令,如:supervised进行模型训练,quantize量化模型以减少内存使用,test进行模型测试,predict预测最可能的标签等,运行结果如下所示:

    >> ./fasttext
    usage: fasttext <command> <args>
    
    The commands supported by fasttext are:
    
      supervised              train a supervised classifier
      quantize                quantize a model to reduce the memory usage
      test                    evaluate a supervised classifier
      predict                 predict most likely labels
      predict-prob            predict most likely labels with probabilities
      skipgram                train a skipgram model
      cbow                    train a cbow model
      print-word-vectors      print word vectors given a trained model
      print-sentence-vectors  print sentence vectors given a trained model
      nn                      query for nearest neighbors
      analogies               query for analogies
      
    
    上述的命令包括:
    supervised: 训练一个监督分类器
    quantize:量化模型以减少内存使用量
    test:评估一个监督分类器
    predict:预测最有可能的标签 
    predict-prob:用概率预测最可能的标签
    skipgram:训练一个 skipgram 模型
    cbow:训练一个 cbow 模型
    print-word-vectors:给定一个训练好的模型,打印出所有的单词向量
    print-sentence-vectors:给定一个训练好的模型,打印出所有的句子向量
    nn:查询最近邻居
    analogies:查找所有同类词
    

    在本节fastText文本分类中,我们主要使用SUPERVISED、TEST和PREDICT命令,在下一小节中我们主要介绍FASTTEXT关于学习单词向量的模型

    3、获取数据及数据预处理

    正如上面所说,我们需要带有标签的数据去训练我们的监督学习的分类器,本教程中,我们使用cooking相关数据构建我们的分类器,因此首先我们下载数据,数据网址为stackexchange,进行如下命令操作:

    >> wget https://dl.fbaipublicfiles.com/fasttext/data/cooking.stackexchange.tar.gz
    >> tar xvzf cooking.stackexchange.tar.gz
    >> head cooking.stackexchange.txt
    

    通过head命令便可看到文档形式,文档的每一行都包含一个标签,标签后面跟着相应的单词短语,所有的标签都以__label__前缀开始,这事fastText便是标签和单词短语的方式,训练的模型便是预测文档中给定单词短语预测其对应的标签

    在训练分类器之前,我们需要将数据分割成训练集和验证集,我们将使用验证集来评估学习到的分类器对新数据的性能好坏,先通过下面命令来查看文档中总共含有多少数据:

    >> wc cooking.stackexchange.txt 
       15404  169582 1401900 cooking.stackexchange.txt
    

    可以看到我们数据中总共包含了15404个示例,我们把文档分成一个包含12404个示例的训练集和一个包含3000个示例的验证集,执行如下命令:

    >> head -n 12404 cooking.stackexchange.txt > cooking.train
    >> tail -n 3000 cooking.stackexchange.txt > cooking.valid
    
    4、使用fastText快速搭建分类器

    上面数据已经准备好了,接下来我们便开始训练我们的模型,首先执行如下命令进行模型的训练:

    >> ./fasttext supervised -input cooking.train -output model_cooking
    Read 0M words
    Number of words:  14598
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 75109  lr: 0.000000  loss: 5.708354  eta: 0h0m 
    

    -input命令选项指示训练数据,-output选项指示的是保存的模型的位置,在训练结束后,文件model_cooking.bin是在当前目录中创建的,model_cooking.bin便是我们保存训练模型的文件

    模型训练好之后,我们可以交互式测试我们的分类器,即单独测试某一个句子所属的类别,可以通过以下命令进行交互式测试:

    >> ./fasttext predict model_cooking.bin -
    

    输入以上命令后,命令行会提示你输入句子,然后我们可以进行如下句子测试:

    Which baking dish is best to bake a banana bread ?

    上面句子可以得到预测的标签是baking,显然这个预测结果是正确的,我们再进行尝试

    Why not put knives in the dishwasher?

    上面句子预测的标签是food-safety,可以看出是不相关的,显然预测的不正确,为了验证学习到的分类模型的好坏,我们在验证集上对它进行测试,观察模型的精准率precision和召回率recall:

    >> ./fasttext test model_cooking.bin cooking.valid                 
    N  3000
    P@1  0.124
    R@1  0.0541
    Number of examples: 3000
    
    5、精准率Precision和召回率Recall

    精准率Precision指的是预测为正样本中有多少是真正的正样本,召回率Recall指的是样本中的正样本有多少被预测正确了,因此精准率看的是预测为某一类的样本中有多少是真正的属于这一类的,而召回率看的是在分类任务中某一类样本是否完全被预测正确,下面通过一个例子来更清楚的认识这个概念,以下面句子为例:

    Why not put knives in the dishwasher?

    上面句子的正式标签有三个,分别是:equipment, cleaning and knives

    然后我们通过模型对上面句子进行预测,执行如下命令:

    >> ./fasttext predict model_cooking.bin - 5
    

    预测得到的结果分别是:food-safety, baking, equipment, substitutions, bread

    可以看出五个预测的标签中只预测正确了一个标签equipment,我们来看,预测的样本中正确的样本所占的比例即是精准率,因此精准率为1/5=0.2;而真实标签中有多少预测正确了即是召回率,因此召回率为1/3=0.33,这样我们应该能明白精准率和召回率的概念了,想更加详细的了解精准率和召回率,请参考维基百科精准率和召回率

    6、模型优化

    上面通过使用默认参数运行fastText训练得到的模型在分类新问题上效果很差,接下来我们通过更改默认参数来提高性能

    (1) 方案一:数据预处理

    查看数据,我们发现有些单词包含大写字母和标点符号,因此改善模型性能的第一步就是应用一些简单的预处理,预处理可以使用命令行工具例如sed、tr来对文本进行简单的标准化操作,执行命令如下:

    >> cat cooking.stackexchange.txt | sed -e "s/\([.\!?,'/()]\)/ \1 /g" | tr "[:upper:]" "[:lower:]" > cooking.preprocessed.txt
    >> head -n 12404 cooking.preprocessed.txt > cooking.train
    >> tail -n 3000 cooking.preprocessed.txt > cooking.valid 
    

    接下来我们在预处理的数据集上进行模型训练并进行测试,命令如下:

    >> ./fasttext supervised -input cooking.train -output model_cooking
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 82041  lr: 0.000000  loss: 5.671649  eta: 0h0m h-14m 
    
    >> ./fasttext test model_cooking.bin cooking.valid 
    N  3000
    P@1  0.164
    R@1  0.0717
    Number of examples: 3000
    

    观察上面的结果,由于对数据预处理,词典变小了,由原来的14K个单词变成了9K,精准率也上升了4%,因此数据预处理起到了一定的效果

    (2) 方案二:更多的训练次数和更大的学习率

    在默认情况下,fastText在训练期间对每个训练用例仅重复使用五次,这太小,因为我们的训练集只有12k训练样例,因此我们可以通过-epoch选项增加每个样例的使用次数,命令如下:

    >> ./fasttext supervised -input cooking.train -output model_cooking -epoch 25 
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 77633  lr: 0.000000  loss: 7.147976  eta: 0h0m
    

    然后测试模型查看效果:

    >> ./fasttext test model_cooking.bin cooking.valid                                        
    N  3000
    P@1  0.501
    R@1  0.218
    Number of examples: 3000
    

    从上面测试效果可以看出,精准率和召回率都有了大幅度提升,可见增加每个样例的使用次数对于数据集少的情况下效果提升明显。另一个增强算法能力是改变模型的学习速度即学习速率,这对应于处理每个示例后模型的更改程度,当学习率为0时意味着模型根本不会发生改变,因此不会学到任何东西,良好的学习率值在0.1-1.0的范围内,下面我们通过设置算法学习率为learning rate = 1.0进行模型训练:

    >> ./fasttext supervised -input cooking.train -output model_cooking -lr 1.0  
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 81469  lr: 0.000000  loss: 6.405640  eta: 0h0m
    
    >> ./fasttext test model_cooking.bin cooking.valid                         
    N  3000
    P@1  0.563
    R@1  0.245
    Number of examples: 3000
    

    可以看到效果比上面增加epoch还要好,下面我们来将二者结合起来:

    >> ./fasttext supervised -input cooking.train -output model_cooking -lr 1.0 -epoch 25
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 76394  lr: 0.000000  loss: 4.350277  eta: 0h0m
    
    >> ./fasttext test model_cooking.bin cooking.valid                                   
    N  3000
    P@1  0.585
    R@1  0.255
    Number of examples: 3000
    

    下面我们来增加一些新的方式来进一步提升模型的性能,看方案三

    (3) 方案三:word n-grams

    此方案中,我们使用单词bigrams而不是仅仅是unigrams来提高模型的性能,这对于词序很重要的分类问题尤其重要,例如情感分析。n-gram是基于语言模型的算法,基本思想是将文本内容按照子节顺序进行大小为N的窗口滑动操作,最终形成窗口为N的字节片段序列。训练模型命令如下:

    >> ./fasttext supervised -input cooking.train -output model_cooking -lr 1.0 -epoch 25 -wordNgrams 2
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 75366  lr: 0.000000  loss: 3.226064  eta: 0h0m 
    
    >> ./fasttext test model_cooking.bin cooking.valid                                                 
    N  3000
    P@1  0.599
    R@1  0.261
    Number of examples: 3000
    

    通过几个步骤,可以看出我们将模型精准率从12.4%提升到了59.9%,总结一下主要包含以下步骤:
    (1) 数据预处理
    (2) 更改样本训练次数epochs(使用参数 –epoch,标准范围[5, 50])
    (3) 更改学习率learning rate(使用参数 –lr,标准范围[0.1-1])
    (4) 使用word n-grams(使用参数 –wordNgrams,标准范围[1-5])

    7、什么是Bigram

    unigram指的是单个不可分割的单元和标记,通常用做模型的输入,并且在不同的模型中unigram可以是单词或是字母,在fastText中,我们是在单词级别上进行训练模型,因此unigram是单词。类似的,bigram值的是两个连续的单词的串联,n-grams指的便是n个单词的串联。举个例子,现在有这样一句话:Last donut of the night,如果是unigrams则是last,donut,of,the,night,而对于bigrams指的是last donut,donut of,of the,the night

    8、提升训练速度

    目前我们在几千个示例中训练我们的模型,训练只需要几秒钟,但如果数据集增大,标签增多,这时模型训练便会变慢,一个让训练变快的方案便是使用分层softmax,而不是使用常规softmax,使用分层softmax是使用参数 –loss hs实现,命令如下:

    >> ./fasttext supervised -input cooking.train -output model_cooking -lr 1.0 -epoch 25 -wordNgrams 2 -bucket 200000 -dim 50 -loss hs
    Read 0M words
    Number of words:  9012
    Number of labels: 734
    Progress: 100.0%  words/sec/thread: 2199406  lr: 0.000000  loss: 1.718807  eta: 0h0m 
    

    此时对于我们当前的数据集,训练速度应该不超过1秒

    9、总结

    本教程中我们简单介绍了如何使用fastText来训练强大的分类器,同时介绍了一些重要的参数选项,通过调参来进行模型优化


    三、fastText教程-单词表示词向量

    现在机器学习中一个十分流行的做法便是用向量表示单词,即词向量化wordEmbedding,这些向量可以捕捉到有关语言的一些隐藏信息,例如语法信息,语义信息等,好的词向量表示可以提升分类器的性能,在本教程中,我们展示如何使用fastText工具来构建词向量,安装fastText过程请参考上一讲

    1、获取数据

    为了计算词向量,我们需要一个大的文本语料库,根据语料库的不同,单词向量也将捕捉到不同的信息,在本教程中,我们关注Wikipedia的文章,当然也可以考虑其他语料库来源,例如新闻活着Webcrawl,下载Wikipedia语料库执行如下命令:

    wget https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2
    

    下载Wikipedia语料库需要挺长时间,如果不使用Wikipedia全部语料库信息,我们可以在Wikipedia前10亿字节信息进行词向量学习,此数据可以在Matt Mahoney网站上下载

    $ mkdir data
    $ wget -c http://mattmahoney.net/dc/enwik9.zip -P data
    $ unzip data/enwik9.zip -d data
    

    这样我们便获得了Wikipedia的部分数据,因为Wikipedia语料库中包含大量的HTML/XML数据,因此需要对数据进行预处理,我们可以使用与fastText自带的wikifil.pl脚本对其进行预处理,这个脚本最初由Matt Mahoney创建,因此可以在下面网址上找到:http://mattmahoney.net/,执行如下命令对数据进行预处理:

    $ perl wikifil.pl data/enwik9 > data/fil9
    

    我们可以执行如下命令检查我们的文件数据:

    $ head -c 80 data/fil9
    anarchism originated as a term of abuse first used against early working class
    

    可以观察到我们的文本经过了很好的处理,接下来可以用文本来学习词向量

    2、训练词向量

    数据集已经取到了,现在我们可以使用如下的简单命令在上述数据集上训练我们的词向量

    $ mkdir result
    $ ./fasttext skipgram -input data/fil9 -output result/fil9
    

    分解上述命令:./fasttext使用skipgram模型调用二进制fastText可执行文件,当然也可以使用cbow模型,-input表示输入数据路径,-output表示训练的词向量模型所在路径,当fastText运行时,屏幕会显示进度和估计的完成时间,程序完成后,结果目录应该出现如下两个文件,可通过下面命令查看:

    $ ls -l result
    -rw-r-r-- 1 bojanowski 1876110778 978480850 Dec 20 11:01 fil9.bin
    -rw-r-r-- 1 bojanowski 1876110778 190004182 Dec 20 11:01 fil9.vec
    

    fil9.bin文件是一个二进制文件,它存储了整个fastText模型,随后可以进行加载,fil9.vec文件是一个包含单词向量的文本文件,每一行对应词汇表中的每个单词,可通过如下命令查看fil9.vec中的信息

    $ head -n 4 result/fil9.vec
    218316 100
    the -0.10363 -0.063669 0.032436 -0.040798 0.53749 0.00097867 0.10083 0.24829 ...
    of -0.0083724 0.0059414 -0.046618 -0.072735 0.83007 0.038895 -0.13634 0.60063 ...
    one 0.32731 0.044409 -0.46484 0.14716 0.7431 0.24684 -0.11301 0.51721 0.73262 ...
    

    从上面结果可见,第一行显示的是单词向量和向量维度,接下来几行是词汇表中所有单词的单词向量,顺序是按照频率降低的顺序进行排序

    3、skipgram VS cbow

    fastText为计算单词表示提供了两种模型:skipgram和cbow,这和word2vec一样,cbow全称:Continuous-bag-of-words,skipgram模型运行机理是通过附近的词来预测目标单词,而cbow模型则是根据目标词的上下文来预测目标词,这里的上下文指的便是目标词周围的固定大小窗口中包含的单词包,下面通过例子便能够体会到上下文的含义。例如:给出这样一个句子:

    Poets have been mysteriously silient on the subject of cheese

    其目标词是slient,skipgram模型是通过目标词附近的词去预测slient,例如subjector, mysteriously, 而cbow模型则是通过目标词的上下文词来预测slient,如:{been, mysteriously, on, the},并且使用单词的向量预测目标,下面一个示例图展示了二者的差异,使用的句子是

    I am selling these fine leather jackets

    在这里插入图片描述

    上面已经使用skipgram模型对数据集进行了训练,如果想用cbow模型训练之行如下命令

    ./fasttext cbow -input data/fil9 -output result/fil9
    

    从实际效果中看,我们会发现skipgram模型对于单词信息的处理效果要优于cbow模型

    4、模型参数调优

    上面的训练都是使用的默认的参数运行fastText,但根据数据的不同,这些参数可能不是最优的,让我们介绍一下子向量的一些关键参数。

    模型中最重要的两个参数是:词向量大小维度、subwords范围的大小,词向量维度越大,便能获得更多的信息但同时也需要更多的训练数据,同时如果它们过大,模型也就更难训练速度更慢,默认情况下使用的是100维的向量,但在100-300维都是常用到的调参范围。subwords是一个单词序列中包含最小(minn)到最大(maxn)之间的所有字符串(也即是n-grams),默认情况下我们接受3-6个字符串中间的所有子单词,但不同的语言可能有不同的合适范围

    $ ./fasttext skipgram -input data/fil9 -output result/fil9 -minn 2 -maxn 5 -dim 300
    

    下面介绍另外两个参数:epoch、learning rate、epoch根据训练数据量的不同,可以进行更改,epoch参数即是控制训练时在数据集上循环的次数,默认情况下在数据集上循环5次,但当数据集非常大时,我们也可以适当减少训练的次数,另一个参数学习率,学习率越高模型收敛的速度就越快,但存在对数据集过度拟合的风险,默认值时0.05,这是一个很好的折中,当然在训练过程中,也可以对其进行调参,可调范围是[0.01, 1],下面命令便尝试对这两个参数进行调整:

    $ ./fasttext skipgram -input data/fil9 -output result/fil9 -epoch 1 -lr 0.5
    

    最后fastText是多线程的,默认情况下使用12个线程,如果你的机器只有更少的CPU核数,也可以通过如下参数对使用的CPU核数进行调整

    $ ./fasttext skipgram -input data/fil9 -output result/fil9 -thread 4
    
    5、打印词向量

    直接从fil9.vec文件中搜索和打印词向量是十分麻烦的,但幸运的是fastText提供了打印词向量的功能,我们可以通过fastText中print-word-vectors功能打印词向量,例如,我们可以使用以下命令打印单词asparagus、pidgey和yellow单词的词向量:

    $ echo "asparagus pidgey yellow" | ./fasttext print-word-vectors result/fil9.bin
    asparagus 0.46826 -0.20187 -0.29122 -0.17918 0.31289 -0.31679 0.17828 -0.04418 ...
    pidgey -0.16065 -0.45867 0.10565 0.036952 -0.11482 0.030053 0.12115 0.39725 ...
    ·0.040719 -0.30155 ...
    

    一个很好的功能是我们可以查询到未出现在数据中的单词,实际上,单词是由字符串的总和组成,只要未知的单词是由已知的字串构成,就可以得到单词的词向量,举个例子下面尝试一下查询拼写出错的单词:

    $ echo "enviroment" | ./fasttext print-word-vectors result/fil9.bin
    

    结果仍然可以查询到词向量,但是至于效果怎么样,我们可以在下一节找到答案

    6、临近词向量查询

    检查单词向量质量的一种简单的方法是查看此此单词的临近词,可以通过临近词比较来查看词向量对于语义的表达。最临近词向量查询可以通过fastText提供的nn功能来实现,例如我们可以通过运行一下命令来查询单词10个最近邻居:

    $ ./fasttext nn result/fil9.bin
    Pre-computing word vectors... done.
    

    然后命令行便会提示我们输入需要查询的词,我们尝试一下asparagus

    Query word? asparagus
    beetroot 0.812384
    tomato 0.806688
    horseradish 0.805928
    spinach 0.801483
    licorice 0.791697
    lingonberries 0.781507
    asparagales 0.780756
    lingonberry 0.778534
    celery 0.774529
    beets 0.773984
    

    从上面结果可以看出效果不错,查询词之间由很大的共性,再尝试查询pidgey,结果如下

    Query word? pidgey
    pidgeot 0.891801
    pidgeotto 0.885109
    pidge 0.884739
    pidgeon 0.787351
    pok 0.781068
    pikachu 0.758688
    charizard 0.749403
    squirtle 0.742582
    beedrill 0.741579
    charmeleon 0.733625
    

    上面提到了如果单词拼写出错可能影响词向量的查询,那如果单词拼写错误,如果查询其临近词结果如何,下面展示一下效果:

    Query word? enviroment
    enviromental 0.907951
    environ 0.87146
    enviro 0.855381
    environs 0.803349
    environnement 0.772682
    enviromission 0.761168
    realclimate 0.716746
    environment 0.702706
    acclimatation 0.697196
    ecotourism 0.697081
    

    可以看出虽然单词拼写出错,但是查询结果还是捕获到了单词的主要信息,拼写出错的单词也与合理的单词匹配,虽然还是有一些影响,但整体方向是正确的。

    为了找到词向量临近的单词,我们需要计算的单词之间的相似度得分。模型训练的单词是由连续的单词向量表示,因此我们可以对其进行相似度的比较,一般情况下,我们使用余弦相似度去衡量两个单词之间的相似度,我们可以计算词汇表中任意单词和所有其他单词之间的相似度,并显示10个最相似单词,当然被查询单词本身肯定排在顶部,相似度为1

    7、单词类比

    在相似度问题中,有时会进行单词类比,例如我们训练的模型能够知道法国是什么,并且知道柏林对于德国来说意味着什么。这个在fastText中是可以做到的,利用单词类比这个功能即可实现,例如下面我们输入三个单词,然后输出单词的类比单词:

    $ ./fasttext analogies result/fil9.bin
    Pre-computing word vectors... done.
    Query triplet (A - B + C)? berlin germany france
    paris 0.896462
    bourges 0.768954
    louveciennes 0.765569
    toulouse 0.761916
    valenciennes 0.760251
    montpellier 0.752747
    strasbourg 0.744487
    meudon 0.74143
    bordeaux 0.740635
    pigneaux 0.736122
    

    上面模型类比功能提供的最可能结果是巴黎,显然是十分准确,下面我们再来看一个不太明显的例子:

    Query triplet (A - B + C)? psx sony nintendo
    gamecube 0.803352
    nintendogs 0.792646
    playstation 0.77344
    sega 0.772165
    gameboy 0.767959
    arcade 0.754774
    playstationjapan 0.753473
    gba 0.752909
    dreamcast 0.74907
    famicom 0.745298
    

    从上面结果可以看出模型认为psx是索尼的游戏手柄,因此nintendo任天堂类比的是gamecube,这个类比也比较合理。当然类比的质量也取决于训练模型的数据集,类比的结果也仅仅在数据集的范围内

    8、 字符n-grams重要性

    利用subword-level信息也即是n-grams对于构建未知单词词向量很有趣,例如Wikipedia中不存在gearshift这个单词,但是我们仍然能够查询到它的临近单词:

    Query word? gearshift
    gearing 0.790762
    flywheels 0.779804
    flywheel 0.777859
    gears 0.776133
    driveshafts 0.756345
    driveshaft 0.755679
    daisywheel 0.749998
    wheelsets 0.748578
    epicycles 0.744268
    gearboxes 0.73986
    

    效果还可以,因为大多数被检索到的单词共享大量的子串,当然也有些特殊的单词比较特殊,例如cogwheel,我们可以看到subword-level对于未知单词查询所起到的效果,但是如果我们在训练模型的时候没有使用subwords这个参数,结果会如何,下面我们便进行尝试,运行以下命令训练没有subwords的模型:

    $ ./fasttext skipgram -input data/fil9 -output result/fil9-none -maxn 0
    

    此时训练的模型保存在result/fil9-non.vec和result/fil9-non.bin,为了表明不加subwords模型的不同,我们再举一个wikipedia中不常见的单词如:accomodation,就类似于accommodation住宿这个单词,下面给出其相似词的查询结果:

    $ ./fasttext nn result/fil9-none.bin
    Query word? accomodation
    sunnhordland 0.775057
    accomodations 0.769206
    administrational 0.753011
    laponian 0.752274
    ammenities 0.750805
    dachas 0.75026
    vuosaari 0.74172
    hostelling 0.739995
    greenbelts 0.733975
    asserbo 0.732465
    

    可以看出结果中的词没有任何意义,大多数词都是不想关的,我们再用使用了subwords的模型测试accomodation的相似词,结果便有明显的差别:

    Query word? accomodation
    accomodations 0.96342
    accommodation 0.942124
    accommodations 0.915427
    accommodative 0.847751
    accommodating 0.794353
    accomodated 0.740381
    amenities 0.729746
    catering 0.725975
    accomodate 0.703177
    hospitality 0.701426
    

    上面结果准确捕捉到相似度很高的accommodation这个单词,同时我们还捕获到语义相关的词如:便利设施amenities和寄宿lodging,因此训练模型加上subwords参数对模型效果有很大的提升

    9、 结论

    在小节中,详细展示了如果在wikipedia上获得词向量,对于其他语言也都可以同样运行,下面网址提供了fastText在词向量上的多个预训练模型,可以参考使用预训练模型网址


    四、常用命令备忘录

    词向量的学习-使用fastText学习词向量执行以下命令:

    $ ./fasttext skipgram -input data.txt -output model
    

    取得词向量-将模型学习得词向量打印到文件中执行如下命令:

    $ ./fasttext print-word-vectors model.bin < queries.txt
    

    文本分类-训练一个文本分类模型执行如下命令:

    $ ./fasttext supervised -input train.txt -output model
    

    当一个模型训练结束后,我们可以通过在测试集上计算精准率Precision和召回率Recall进行模型评估,执行如下命令:

    $ ./fasttext test model.bin test.txt 1
    

    为了直接预测一段文本最可能的k个标签,执行如下命令:

    $ ./fasttext predict model.bin test.txt k
    

    为了直接预测一段文本的k个最可能的标签及其相关概率大小,可以执行如下命令:

    $ ./fasttext predict-prob model.bin test.txt k
    

    如果想要计算句子或段落的向量表示,执行如下命令:

    $ ./fasttext print-sentence-vectors model.bin < text.txt
    

    为了创建一个内存更小的模型可以执行如下命令

    $ ./fasttext quantize -output model
    

    所有其他的命令都类似下面test命令

    $ ./fasttext test model.ftz test.txt
    

    五、模型可选参数列表及默认值

    $ ./fasttext supervised
    Empty input or output path.
    
    The following arguments are mandatory:
      -input              training file path
      -output             output file path
    
      The following arguments are optional:
      -verbose            verbosity level [2]
    
      The following arguments for the dictionary are optional:
      -minCount           minimal number of word occurrences [5]
      -minCountLabel      minimal number of label occurrences [0]
      -wordNgrams         max length of word ngram [1]
      -bucket             number of buckets [2000000]
      -minn               min length of char ngram [3]
      -maxn               max length of char ngram [6]
      -t                  sampling threshold [0.0001]
      -label              labels prefix [__label__]
    
      The following arguments for training are optional:
      -lr                 learning rate [0.05]
      -lrUpdateRate       change the rate of updates for the learning rate [100]
      -dim                size of word vectors [100]
      -ws                 size of the context window [5]
      -epoch              number of epochs [5]
      -neg                number of negatives sampled [5]
      -loss               loss function {ns, hs, softmax} [ns]
      -thread             number of threads [12]
      -pretrainedVectors  pretrained word vectors for supervised learning []
      -saveOutput         whether output params should be saved [0]
    
      The following arguments for quantization are optional:
      -cutoff             number of words and ngrams to retain [0]
      -retrain            finetune embeddings if a cutoff is applied [0]
      -qnorm              quantizing the norm separately [0]
      -qout               quantizing the classifier [0]
      -dsub               size of each sub-vector [2]
    

    默认值可能因模型不同,例如单词表示模型skip gram和cbow使用默认的minCount为5


    六、fastText中常见问题汇总

    1、什么是fastText

    fastText是一个用于文本分类和词向量表示的库,它能够把文本转化成连续的向量然后用于后续具体的语言任务,目前教程较少!

    2、为什么训练的模型非常大

    fastText对字和字符串使用hash表,hash表的大小将直接影响模型的大小,可以通过选项-hash来减少词汇hash表的大小,一个可选的好参数时20000。另一个影响模型大小重要的因素是训练向量的维度大小(-dim),如果维度缩小模型将大大减小,但同时也会很大程度影响模型的性能,因为向量维度越大则捕获的信息越多,当然还有一种将模型变小的方法是使用量化选项(-quantize),命令如下所示:

    ./fasttext quantize -output model
    
    3、模型中使用单词短语而不是单个单词最佳方式是什么

    目前使用单词短语或句子最好的方式是使用词向量的bow(bag of words),另一种方式例如New York,我们可以将其处理成New_York也会有帮助

    4、为什么fastText甚至可以为语料库中未出现的单词产生词向量

    fastText一个重要的特性便是有能力为任何单词产生词向量,即使是未出现的,组装的单词。主要是因为fastText是通过包含在单词中的子字符substring of character来构建单词的词向量,正文中也有论述,因此这种训练模型的方式使得fastText可以为拼写错误的单词或者连接组装的单词产生词向量

    5、为什么分层softmax在效果上比完全softmax略差

    分层softmax是完全softmax的一个近似,分层softmax可以让我们在大数据集上高效的建立模型,但通常会以损失精度的几个百分点为代价,

    6、可以在GPU上运行fastText项目吗

    目前fastText仅仅可运行在CPU上,但这也是其优势所在,fastText的目的便是要成为一个高效的CPU上的分类模型,可以允许模型在没有CPU的情况下构建

    7、可以使用python语言或者其他语言使用fastText嘛

    目前在GitHub上有很少的关于fastText的其他语言实现的非官方版本,但可以负责任的说,是可以用tensorflow实现的

    8、可以在连续的数据集上使用fastText吗

    不可以,fastText仅仅是用于离散的数据集,因此无法直接在连续的数据集上使用,但是可以将连续的数据离散化后使用fastText

    9、数据中存在拼写错误,我们需要对文本进行规范化处理吗

    如果出现的频率不高,没有必要,对模型效果不会有什么影响

    10、在模型训练时遇到了NaN,为什么会这样

    这种现象是可能出现的,很大原因是因为你的学习率太高了,可以尝试降低一下学习率直到不再出现NaN

    11、系统无法编译fastText,怎么处理

    尝试更新一下编译器版本,很大可能就是因为编译器太旧了

    12、如何完全重现fastText的运行结果,为什么每次运行的结果都有些差异

    当多次运行fastText时,因为优化算法异步随机梯度下降算法或Hogwild,所以每次得到的结果都会略有不同,如果想要fastText运行结果复现,则必须将参数thread设置为1,这样你就可以在每次运行时获得完成相同的性能

    展开全文
  • 实现一级菜单二级菜单:

    实现一级菜单:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <title>menu1.html</title>
    	
        <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
        <meta http-equiv="description" content="this is my page">
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
        
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
    
    
    <style type="text/css">
    
    body{
      font-family: "宋体";
      font-size: 12px;
      line-height: 1.5;
    }
    
    a{
      color:#000;
    }
    
    a:HOVER{
      color:#F00;
    }
    
    .menu{
      width:100px;
      border:1px solid #CCC;
      /* border:1px solid red; */
      background-color: silver;
    }
    
    
    .menu ul{
    margin:0px;
    padding:0px;
    background-color: pink;
    
    }
    
    .menu li{
    list-style-type: none;
    background-color: #eee;
    padding:0px 8px;
    height:26px;
    line-height: 26px;
    border-bottom:1px solid #CCC;
    
    }
    </style>
    
      </head>
      
      <body>
        <div class="menu">
          <ul>
            <li><a href="#">首页</a></li>
            <li><a href="#">网页版布局</a></li>
            <li><a href="#">div+css教程</a></li>
            <li><a href="#">div+css实例</a></li>
            <li><a href="#">常用代码</a></li>
            <li><a href="#">站长杂谈</a></li>
            <li><a href="#">技术文档</a></li>
            <li><a href="#">资源下载</a></li>
            <li><a href="#">图片素材</a></li>
          </ul>
        </div>
        
        
      </body>
    </html>
    




    显示效果:



    二级菜单的实现:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <title>menu1.html</title>
    	
        <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
        <meta http-equiv="description" content="this is my page">
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
        
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
    
    
    <script type="text/javascript">
      var startList = function() {
          if (document.all&&document.getElementById) {
             navRoot = document.getElementById("menu");/* 得到id */
             var allli = navRoot.getElementsByTagName("li");/*得到li所有的元素  */
             for (var i=0; i<allli.length; i++) {
                    var node = allli[i];
                    node.οnmοuseοver=function() {/*注册函数  */
                    this.className+=" current";
                    };
                    node.οnmοuseοut=function() {/*注册函数  */
                    this.className=this.className.replace(" current", "");
                    };
    }
    }
    
    };
    window.οnlοad=startList;/* 加载完毕,执行 */
    </script>
    
    
    <style type="text/css">
    
    body{
      font-family: "宋体";
      font-size: 15px;/* 字体的大小 */
      line-height: 1.5;/* line-height 属性设置行间的距离(行高)。 */
    }
    
    
    
    a{
      color:#f0f;/*正常的a标签的字体元素  */
      text-decoration: none;/* 取消下划线 */
    }
    
    /*鼠标悬浮时,字体的颜色  */
    a:HOVER{
      color:#F00;
    }
    
    /* id为menu的菜单 */
    #menu{
      width:200px;/*设置宽度  */
      /* border:1px solid #CCC; */
      border:2px solid blue;/* 设置边框 */
      /* background-color: silver; */
      background-color: red; /*背景颜色为红色  */
      border-bottom: none;/*下边框的宽度  */
    }
    
    
    #menu ul{
    margin:0px;/*ul的外边距  */
    padding:0px;/*ul的内边距  */
    width:120px;/* 块元素的宽度 */
    background-color: pink;/*设置背景颜色  */
    
    }
    
    #menu ul li{
      list-style-type: none;
      background-color: #eee;
      /* background-color: red; */
      width:90px;
      padding:0px 8px;
      height:26px;
      line-height: 26px;
      border-bottom:1px solid #CCC;
      /* border-bottom:1px solid red; */
      position:relative;
    
    }
    
    
    #menu ul li ul{
       position:absolute;/*绝对定位  */
       left:100px;/* 向右移动100px */
       top:0px;/* 向下移动0px */
       display:none;/*默认不显示  */
       width:100px;/*宽度  */
       border:1px solid #CCC;/* 边框 */
       border-bottom: none;
    }
    
    
    
    #menu ul li.current ul{
      display:block;/*以块元素显示  */
    }
    
    #menu ul li:hover ul{
    	display:block;/*以块元素显示  */
    }
    
    
    
    </style>
    
      </head>
      
      <body>
        <div id="menu">
          <ul>
            <li><a href="@#">首页</a></li>
            <li><a href="#">网页版布局</a>
           <ul>
             <li><a href="#">自适用宽度</a></li>
             <li><a href="#">固定宽度</a></li>
           </ul>
           </li>
           
            <li><a href="#">div+css教程</a>
            <ul>
             <li><a href="#">新手入门教程</a></li>
             <li><a href="#">视频教程</a></li>
             <li><a href="#">常见问题</a></li>
            </ul>
            </li>
            
            <li><a href="#">div+css实例</a></li>
            <li><a href="#">常用代码</a></li>
            <li><a href="#">站长杂谈</a></li>
            <li><a href="#">技术文档</a></li>
            <li><a href="#">资源下载</a></li>
            <li><a href="#">图片素材</a></li>
          </ul>
        </div>
        
        
      </body>
    </html>
    


    显示效果如下:




    代码里面都有注释,就不做过多的解释了。

    http://blog.csdn.net/j903829182/article/details/38735639






















































































    展开全文
  • 上篇介绍了 Canvas 的基本操作,绘制圆、矩形、椭圆、弧等,Canvas 除了这些操作还有两个比较重量的绘制能力 Text(文本) Path(路径),今天就先看一下 Text 的绘制 先看一下 Paint 对于 Text 都提供了那些设置吧...

    开篇

    上篇介绍了 Canvas 的基本操作,绘制圆、矩形、椭圆、弧等,Canvas 除了这些操作还有两个比较重量级的绘制能力 Text(文本)Path(路径),今天就先看一下 Text 的绘制

    先看一下 Paint 对于 Text 都提供了那些设置吧:

    Paint 对于 Text 的相关设置

    • 普通设置

      paint.setStrokeWidth(5):设置画笔宽度
      paint.setAntiAlias(true):设置是否使用抗锯齿功能,如果使用,会导致绘图速度变慢
      paint.setStyle(Paint.Style.FILL):设置绘图样式,对于设置文字和几何图形都有效,可取值有三种 :1、Paint.Style.FILL:填充内部 2、Paint.Style.FILL_AND_STROKE:填充内部和描边 3、Paint.Style.STROKE:仅描边
      paint.setTextAlign(Align.CENTER):设置文字对齐方式
      paint.setTextSize(12):设置文字大小

    • 样式设置

      paint.setFakeBoldText(true):设置是否为粗体文字
      paint.setUnderlineText(true):设置下划线
      paint.setTextSkewX((float) -0.25):设置字体水平倾斜度,普通斜体字是 -0.25
      paint.setStrikeThruText(true):设置带有删除线效果

    • 其他设置

      paint.setTextScaleX(2):设置水平拉伸,高度不会变

    使用 Canvas 绘制文字

    • 1、普通水平绘制

    void drawText (String text, float x, float y, Paint paint)
    void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
    void drawText (String text, int start, int end, float x, float y, Paint paint)
    void drawText (char[] text, int index, int count, float x, float y, Paint paint)

    说明:
    - 第一个构造函数时最简单的构造函数
    - 第三、四个构造函数:实现截取一部分字体绘图
    - 第二个构造函数最强大,因为传入的可以是 CharSequence 类型字体,所以可以实现绘制带图片的扩展文字,而且还能截取一部分绘制

    这几个函数都比较简单,就不在具体演示了

    • 2、指定各个文字位置

    void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
    void drawPosText (String text, float[] pos, Paint paint)

    参数:
    - char[] text:要绘制的文字数组
    - int index:第一个要绘制的文字的索引
    - int count:要绘制的文字的个数,用来算最后一个文字的位置,从第一个绘制的文字开始算起
    - float[] pos:每个字体的位置,两个为一组

    onDraw 方法:

        private void init() {
            //初始化画笔
            paint = new Paint();
            paint.setTextSize(50);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawPosText("巴扎黑", new float[]{100, 100, 100, 200, 100, 300}, paint);
        }

    image.png

    • 3、沿路径绘制

      void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
      void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)


    参数:
    • Path path:文字的绘制路径
    • char[] text:要绘制的文字数组
    • int index:第一个要绘制的文字的索引
    • int count:要绘制的文字的个数,用来算最后一个文字的位置,从第一个绘制的文字开始算起
    • float hOffset:与路径起始点的水平偏移距离
    • float vOffset:与路径中心点的垂直偏移量

    onDraw 方法:

        private void init() {
            //初始化画笔
            paint = new Paint();
            paint.setTextSize(50);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            path = new Path();
            //设置路径,以圆作为我们文本显示的路线
            path.addCircle(300, 300, 200, Path.Direction.CW);  //路径的绘制方式 CW 表示正序绘制,CCW表示倒序绘制
    
            path1 = new Path();
            path1.addCircle(800, 300, 200, Path.Direction.CW);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //绘制出路径原型,方便后面比较
            canvas.drawPath(path, paint);
            canvas.drawPath(path1, paint);
            //把文字绘制在要显示的路径上,默认不偏移
            canvas.drawTextOnPath("搞笑我们是认真的!!!", path, 0, 0, paint);
            //把文字绘制在要显示的路径上,路径起始点偏移150,中心垂直点偏移 50
            canvas.drawTextOnPath("搞笑我们是认真的!!!", path1, 150, 50, paint);
        }

    效果图
    image.png

    可以看到,两个文本显示的位置有点不一样,这就是我们设置的偏移量导致的,第二个文本的起始位置比第一个文本偏移了150个像素,第二个文本是在路径的外面,而第一个文本是在路径的里面,这就是我们设置的中心垂直偏移导致的

    drawText 绘制技巧

    我们都知道绘制文本只需要调用 Canvas 的 drawText 方法就可以在任何位置绘制我们想要的文字,但是你知道你知道 drawText 方法绘制文本时是依据什么条件进行绘制的么? 我们来看一个例子吧:

    onDraw 方法:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //标准线,先绘制一条线出来,等会你就会发现一个非常不可思议的事情
           canvas.drawLine(100,100,1000,100,paint);
    
           canvas.drawText("gaoxiaowomenshirenzhende....”,200,100,paint);
    
        }

    效果图:

    image.png

    你会发现一个很有意思的事情,就是我们明明给我们要绘制的文本设置的位置是 (200,100),而我们绘制的线的位置 Y 坐标是在 100 的位置上,为什么我们绘制的文本的第一个字母 “g” 会出现在标准线的下方呢?

    为什么会出现这种情况呢?下面慢慢介绍

    四线格与基线

    还记得我们小时候写拼音使用的四线格本子么?带你们回忆回忆童年,那时候我们都知道要写在四线格内

    image.png

    那么问题来了,其实 Canvas 在调用 drawText 方法绘制文本时,也是有规则的,这个规则就是基线

    image.png

    也就是说 Canvas 在调用 drawText 绘制文本时,是根据基线的位置来确定要绘制的 Text 的位置的,要想把 Text 绘制到正确的位置上,必须知道基线的位置

    canvas.drawText() 与 基线

    下面我们来重新审视一下 canvas.drawText() 这个函数

    /** 
    * text:要绘制的文字 
    * x:绘制原点x坐标 
    * y:绘制原点y坐标 
    * paint:用来做画的画笔 
    */  
    public void drawText(String text, float x, float y, Paint paint)  

    上面这个函数是绘制文本最常用的方法,我们之前对传递进去的(x,y)都产生了误解,以为(x,y) 就是我们所要绘制的文字的左上角的坐标,其实不然,我们传进去的(x,y)其中的 y 表示的其实是上图中基线的位置, 而 x 当然也不可能是你想象的 x 了,你以为 x 表示的就是文本开始绘制的位置么?骚年,你还是太年轻了,来看一下吧

    paint.setTextAlign(Paint.Align.XXX);

    我们知道这个函数是用来设置文字的对齐方式的,它的 取值有三个,左对齐(Panit.Align.LEFT)、居中对齐(Paint.Align.CENTER)和 右对齐(Paint.Align.RIGHT ),我们分别来试一下吧,看结果说话:

            @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            //标准线,以位置为 (5500) 和 (5501500)绘制一条标准线,X 轴距离坐标轴原点距离为 550
            canvas.drawLine(550,0,550,1500,paint);
    
            //左对齐
            paint.setTextAlign(Paint.Align.LEFT);
            //基线位置
            canvas.drawLine(0,200,1500,200,paint);
            canvas.drawText("搞笑我们是认真的",550,200,paint);
    
            //居中对齐
            paint.setTextAlign(Paint.Align.CENTER);
            //基线位置
            canvas.drawLine(0,300,1500,300,paint);
            canvas.drawText("搞笑我们是认真的",550,300,paint);
    
            //右对齐
            paint.setTextAlign(Paint.Align.RIGHT);
            //基线位置
            canvas.drawText("搞笑我们是认真的",550,400,paint);
            canvas.drawLine(0,400,1500,400,paint);
    
        }

    效果图:
    image.png

    上面我们先绘制了一条垂直的标准线,X 坐标距离原点的距离为 550,
    然后以这个标准线和设置的对齐方式开始绘制文本(搞笑我们是认真的),发现不同的对齐方式显示的位置差别很大,这是为什么呢?其实当我们传入(x,y)时,x 指代的只是一个相对距离,并不是要绘制的文本的 X 轴的坐标

    • 左对齐时:
      当对齐方式为左对齐时,x 指代的就是文本要绘制的 X 轴的坐标,(x,y)就是要绘制的文本的起始位置
    • 居中对齐时:
      当对齐方式为居中对齐时,x 指代的是一个相对距离,这个相对的是原点(0,0)的距离,要绘制的文本会以这个距离标准居中显示
    • 右对齐时:
      原理同上

    drawText的四线格与 FontMetrics

    前面我们提到在绘制 Text 是依据 **基线** 来进行绘制的,其实系统在绘制 Text 时,还有其他线存在的,而 基线 只是用来绘制 Text 的一个标准线 ![image.png](https://upload-images.jianshu.io/upload_images/11455341-8d2059619f878345.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 从上图可以看出,除了 基线 以外,还有另外 四 条线,分别是 top、bottom、ascent、descent、它们所表示的意思分别是:
    • ascent:系统建议的,绘制单个字符时,字符应当的最高高度所在线
    • descent:系统建议的,绘制单个字符时,字符应当的最低高度所在线
    • top:可绘制的最高高度所在线
    • bottom:可绘制的最低高度所在线

    光从字面意思可能很难理解这几个值到底是什么意思,没事别着急,我们举个例子来分析下,我们来看一下电视的显示,有用过视频处理工具的同学(比如 premiere、AE、绘声绘影等)应该都会知道,在制作视频时,视频显示位置都会有一个安全区域狂,如下所示:

    image.png

    黑色部分表示电视屏幕,红色框就表示安全区域框。
    这个安全区域框是用来干嘛的?这个安全框就是系统推荐给我们的显示区域,虽然说我们可以将电视屏幕的每个区域都显示图像,但是由于制式不同,每个国家的屏幕大小并不一定和我们这里的屏幕大小一致,当遇到不一致时,就会裁剪。但系统给我们推荐的显示区域是无论那种制式都是可以完整显示出来的,所以我们在制作视频时,尽量要把要显示的图像放在系统推荐的显示区域内。

    同样,我们在绘制文字时, ascent 是推荐的绘制文字的最高高度,就表示在绘制文字时,尽力要在这个最高高度以下绘制文字,descent 是推荐的绘制文字的最低高度线,同样表示是在绘制文字时尽量在这个 descent 线以上来绘制文字。而 top 线则表示该文字可以绘制的最高高度线,bottom 则表示该文字可以绘制的最低高度线。ascent 和 descent 是系统建议的绘制高度,而 top 和 bottom 则是物理上屏幕高度,他们的差别与我们上面说的视频处理的安全框和屏幕是一个道理

    FontMetrics

    上面已经对top、bottom、ascent、descent、baseline 这五条线进行了介绍,那么这五条线之间是什么关系?以及这五条线的位置是如何计算出来呢?

    Android 为我们提供了一个类:FontMetrics ,它里面有四个成员变量:

    • FontMetrics.ascent
    • FontMetrics.descent
    • FontMetrics.top
    • FontMetrics.bottom

    它们之间的关系如下:

    • ascent = ascent 线的 Y 坐标 - baseline 线的 Y 坐标
    • descent = descent 线的 Y 坐标 - baseline 线的 Y 坐标
    • top = top 线的 Y 坐标 - baseline 线的 Y 坐标
    • bottom = bottom 线的 Y 坐标 - baseline 线的 Y 坐标

    我们来看图分析下,看是不是这么个情况:

    image.png

    从这个图中,我们先说明两个问题,然后在讨论上面的公式:

    • X 轴 和 Y 轴 都是有正反方向的,X 轴是向右是正方向,Y 轴是向下是正方向,所以越往下表示 Y 轴坐标越大
    • 千万不要讲 FontMetrics 中的 ascent、descent、top、bottom 与现实中的 ascent、descent、top、bottom 所在线弄混淆了,这几条线是真实存在的,而 FontMetrics 中的 ascent、descent、top、bottom 这些变量的值就是为了计算着几条线的位置的,下面我们就利用这几个变量来计算出这几条线应该处于的位置,并绘制出来:

    其实上面的几个公式得到的 ascent、descent、top、bottom 就是 baseline 到各个线的位置,不过对于 top 和 ascent 来说,baseline 线位于这两条线的下方,所以 baseline 线的 Y 坐标肯定大于 top 和 ascent 线的 Y 坐标,所以 ascent 和 top 的值肯定是负的,而 bottom 和 descent 这两条线都处于 baseline 线下方,所以都是正的,下面我们通过上面的公式分别来求出各个线的 Y 坐标,然后通过 Y 坐标把各条线绘制出来

    • ascent Y 坐标 = baseline Y 坐标 + FontMetrics.ascent
    • descent Y 坐标 = baseline Y 坐标 + FontMetrics.descent
    • top Y 坐标 = baseline Y 坐标 + FontMetrics.top
    • bottom Y 坐标 = baseline Y 坐标 + FontMetrics.bottom

    FontMetrics 对象的获取

            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            float ascent = fontMetrics.ascent;
            float descent = fontMetrics.descent;
            float top = fontMetrics.top;
            float bottom = fontMetrics.bottom;

    从这里可以看到,通过 paint.getFontMetrics() 得到对应的 FontMetrics 对象。这里还有另外一个 FontMetrics 同样的类叫做 FontMetricsInt 它的意义与 FontMetrics 完全相同,只是得到的类型不一样而已。FontMetericInt 中的四个成员变量的值都是 Int 类型,而 FontMetrics 得到的四个成员变量的值都是 float 类型的。

    绘制出各个线的位置

    效果图如下:

    image.png

    onDraw 方法:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int baselineX = 10;      //基线的 X 轴
            int baselineY = 300;    //基线的 Y 轴
    
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            //获取各个线距离 baseline 线的距离
            float ascent = fontMetrics.ascent;
            float descent = fontMetrics.descent;
            float top = fontMetrics.top;
            float bottom = fontMetrics.bottom;
    
            //绘制文本
            canvas.drawText("搞笑我们是认真的!!!", baselineX, baselineY, paint);
    
            paint.setStrokeWidth(3);
    
            //绘制基线
            paint.setColor(Color.RED);
            canvas.drawLine(baselineX, baselineY, 1000, baselineY, paint);
    
            //绘制 ascent 线
            paint.setColor(Color.BLUE);
            canvas.drawLine(baselineX, baselineY + ascent, 1000, baselineY + ascent, paint);
    
            //绘制 descent 线
            paint.setColor(Color.BLACK);
            canvas.drawLine(baselineX, baselineY + descent, 1000, baselineY + descent, paint);
    
            //绘制 top 线
            paint.setColor(Color.GREEN);
            canvas.drawLine(baselineX, baselineY + top, 1000, baselineY + top, paint);
    
            //绘制 bottom 线
            paint.setColor(Color.YELLOW);
            canvas.drawLine(baselineX, baselineY + bottom, 1000, baselineY + bottom, paint);
        }

    上面代码中已经注释的很清楚了,就不在单独拿出来进行介绍了

    获取所绘制文字的宽度、高度和最小矩形

    在这里,我们将搞定如何获取或绘制字符串所占区域的高度、宽度以及仅仅包裹字符串的最小矩形。我们先来看下示例图:

    image.png

    从这张图中,文字底部的绿色框就是所绘制字符串占据的大小,我们要求的宽度和高度也就是这个绿色框的宽度和高度。

    从图中也可以看到,红色框部分,它的宽和高紧紧包裹着字符串,所以红色框就是我们要求的最小矩形,既能包裹字符串的最小矩形

    字符串所占高度和宽度

    • 1、高度
      字符串所占高度很容易得到,直接用 FontMetrics.bottom - FontMetrics.top(因为 FontMetrics.top 是负值) 就是字符串所占的高度:

    • 2、宽度是非常容易得到的,直接利用下面的函数就可以得到:

    float width = paint.measureText(String text);

    使用示例如下:

            paint = new Paint();
            paint.setTextSize(100);     //单位为 sp
            float width = paint.measureText("搞笑我们是认真的”);
    • 最小矩形
      要获取最小矩形,也是通过系统函数来获取的,函数定义如下:
    /** 
     * 获取指定字符串所对应的最小矩形,以(0,0)点所在位置为基线 
     * @param text  要测量最小矩形的字符串 
     * @param start 要测量起始字符在字符串中的索引 
     * @param end   所要测量的字符的长度 
     * @param bounds 接收测量结果 
     */  
    public void getTextBounds(String text, int start, int end, Rect bounds);  

    代码示例:

            paint.setTextSize(100);     //单位为 sp
            String text = "搞笑我们是认真的”;
            Rect rect = new Rect();
            paint.getTextBounds(text, 0, text.length(), rect);

    我们看一下输出结果:

    image.png

    可以看到这个矩形的left、top、right、bottom 分别为 3、-82、792、11 ,可能大家会疑惑为什么 top 会为负数呢?其实从代码中我们也可以得出结论,我们并没有给 getTextBounds() 方法传递基线位置,那它就是以(0,0)为基线来得到这个最小矩形的!所以这个最小矩形的位置就是以(0,0)为基线的结果。

    既然已经拿到了最小矩形的left、top、right、bottom,并且知道这个最小矩形是以(0,0)为基线绘制的,那么我们想要把这个矩形绘制在 Text 显示的位置,那么也很简单,只需要加上 baseline 的距离就OK了 我们来试一下:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int baselineX = 100;      //基线的 X 轴
            int baselineY = 300;    //基线的 Y 轴
    
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            //获取各个线距离 baseline 线的距离
            float ascent = fontMetrics.ascent;
            float descent = fontMetrics.descent;
            float top = fontMetrics.top;
            float bottom = fontMetrics.bottom;
    
            float height = bottom - top;
    
            String text = "搞笑我们是认真的”;
            Rect rect = new Rect();
            paint.getTextBounds(text, 0, text.length(), rect);
    
            paint.setColor(Color.YELLOW);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawRect(rect.left +baselineX, baselineY + rect.top, rect.right+baselineX, rect.bottom + baselineY, paint);
    
            //绘制文本
            paint.setColor(Color.RED);
            canvas.drawText(text, baselineX, baselineY, paint);
        }

    效果图:

    image.png

    完整代码如下:

        private void init() {
            //初始化画笔
            paint = new Paint();
            paint.setTextSize(100);
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            paint.setTextSize(100);     //单位为 sp
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int baselineX = 100;      //基线的 X 轴
            int baselineY = 300;    //基线的 Y 轴
    
            String text = "搞笑我们是认真的";
    
            //获取当前线到baseline线的距离
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            float top = fontMetrics.top;    //为负值
            float bottom = fontMetrics.bottom;
            //获取字符串所占高度
            float height = fontMetrics.bottom - fontMetrics.top;
    
            //获取字符串所占宽度
            float width = paint.measureText(text);
    
            //绘制字符串所占区域
            paint.setColor(Color.BLUE);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawRect(baselineX, baselineY + top, width + baselineX, bottom + baselineY, paint);
    
            //获取最小矩形  默认是以(00)为基线获取,所以要想把最小矩形绘制到正确位置,需要 + baseline Y 
            Rect rect = new Rect();
            paint.getTextBounds(text, 0, text.length(), rect);
    
            paint.setColor(Color.YELLOW);
            paint.setStyle(Paint.Style.FILL);
            int left = rect.left + baselineX;
            top = baselineY + rect.top;
            int right = rect.right + baselineX;
            bottom = rect.bottom + baselineY;
            //绘制最小矩形
            canvas.drawRect(left, top, right, bottom, paint);
    
            //绘制文本
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawText(text, baselineX, baselineY, paint);
        }

    完事,终于搞完了,后面开始 Path 绘制,贝塞尔曲线我来了……..

    展开全文
  • 进制文件文本文件和二进制数据

    千次阅读 2015-05-27 17:48:45
    进制文件进制数据文本文件个人简单理解

    我们知道计算机是用二进制来做运算处理的,所以所有存储在计算机里面的东西都是二进制的。

    我也知道这句话,但为什么总是听到别人说”二进制“文件和”文本“文件呢? 按照上面那句话来说计算机里面存放的东西都应该是二进制的。

    也就是0101的组合。

    其实计算机底层存储的都是二进制的”数据“,而不是二进制的文件。

    列举一个二进制文件如下:
    00000000h:0F 01 00 00 0F 03 00 00 12 53 21 45 58 62 35 34; .........S!EXb54
    00000010h:41 42 43 44 45 46 47 48 49 47 4B 4C 4D 4E 4F 50; ABCDEFGHIGKLMNOP
    以前我看到这个二进制文件的时候非常犯傻老钻牛角尖,我理解的二进制不应该是下面的样子吗!

    00000000 00000001 10001010 11011101


    其实硬盘里面存放的都属于”数据“,而存在计算机里面的数据(无论什么数据)都是以二进制的形式趴在”物理“磁盘上的。我们平时说的二进制文件或者是文本文件是站在操作系统层面上讲的,这里面的二进制文件存在硬盘里面是0101,但我们在操作系统上面看到该文件的内容就不一定是0101这样,我一直搞混这个概念。

    下面我们再解释一下二进制文件和文本文件的区别:

    将文件看作是由一个一个字节(byte)组成的,那么文本文件中的每个字节的最高位都是0,也就是说文本文件使用了一个字节中的七位来表示所有的信息,而二进制文件则是将字节中的所有位都用上了。这就是两者的区别。文件按照文本方式或者二进制方式打开,两者会有什么不同呢?其实不管是二进制文件也好,还是文本文件也好,都是一连串的0和1,但是打开方式不同,对于这些0和1的处理也就不同。如果按照文本方式打开,在打开的时候会进行translate,将每个字节转换成ASCII码,而以按照二进制方式打开的话,则不会进行任何的translate;最后就是文本文件和二进制文件在编辑的时候,使用的方式也是不同的。譬如,你在记事本中进行文本编辑的时候,你进行编辑的最小单位是字节(byte);而对二进制文件进行编辑的话,最小单位则是位(bit),当然我们都不会直接通过手工的方式对二进制文件进行编辑了。

    通过以上的描述我们应该知道二进制文件是什么意思了吧!


    以上内容均是个人理解总结,如有不对的地方请指出。

    展开全文
  • 若该文为原创文章,未经允许不得转载 原博主博客地址:...本文章博客地址: 各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究 目录 前话 文本光标接口 概述 基于光标的编辑 ...
  • 总的来讲,个完整的文本分类器主要由两个阶段,或者说两个部分组成:是将文本向量化,将个字符串转化成向量形式;是传统的分类器,包括线性分类器,SVM, 神经网络分类器等等。之前看的THUCTC的技术栈是使用 ...
  • 鬼吹灯文本挖掘1:jieba分词CountVectorizer向量化鬼吹灯文本挖掘2:wordcloud 词云展示鬼吹灯文本挖掘3:关键词提取使用sklearn 计算TF-IDF矩阵鬼吹灯文本挖掘4:LDA模型提取文档主题 sklearn LatentDirichlet...
  • 架构运转过程的分析特定于Lotus Notes平台,其后的关于进制文件和文本文件的讨论则具有普遍性。(严格地说,XPages指的是IBM基于Lotus DominoJSF的快速开发技术,XPage则指单个页面,两者的首两个字母都大写。...
  • Python全国二级等级考试(2019)

    万次阅读 多人点赞 2019-03-19 08:40:24
    最后送给报名了3月份python计算机二级同学份福利:**2019年3月二级Python考试模拟软件**,同时也预祝大家都能顺利通过,快快转发分享给你的同学们一起刷波题吧~
  • AXURE8.0制作二级菜单三级菜单

    千次阅读 2019-08-11 15:27:31
    Axure 二级菜单 三级菜单
  • 一级标题居中,二级标题固定缩进

    千次阅读 2017-07-27 11:25:02
    一级二级标题为自定义长度,且一级标题居中,二级标题在一级标题的基础上缩进3个汉字的距离。
  • Python大数据分析系列博客,包括网络爬虫、可视化分析、GIS地图显示、情感分析、舆情分析、主题挖掘、威胁情报溯源、知识图谱、预测预警及AINLP应用等。前文分享了疫情相关新闻数据爬取,并进行中文分词处理及文本...
  • 计算机二级Python学习笔记(

    万次阅读 多人点赞 2018-07-30 00:17:36
    本来PHP还学艺不精,又报了计算机二级Python的考试,还有个半月的时间,抓紧买了高教社的这两本书,今天正式开始学习这个叼炸天的语言,虽然没法世界上最好的语言PHP相提并论,但是也值得学。 虽然先看蓝K...
  • 若该文为原创文章,未经允许不得转载 原博主博客地址:...本文章博客地址: 各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究 目录 前话 富文本处理 概述 富文本文档结构 ...
  • 文本文件与进制文件区别

    千次阅读 2018-11-15 13:50:18
    为基本结构的种信息组织存储方式。  2)进制文件:这类文件以文本进制形式存储在计算机中,用户一般不能直接读懂它们,只有通过相应的软件才能将其显示出来。进制文件一般是可执行程序、图形、图像...
  • python图片转为进制文本

    万次阅读 热门讨论 2018-11-06 00:05:21
    我在研究机器学习的过程中,给的数据集是手写数字图片被处理后的由0,1表达的txt文件,今天写写关于图片转化为进制txt文件的python实践 在这里,我们使用python中的Pillow库,Pillow库中有个很重要的类是...
  • 文本分类入门()文本分类问题的定义 文本分类系列文章,从文本分类问题的定义开始,主要讲解文本分类系统的构成,主流的统计... 文本(以下基本不区分“文本“文档”两个词的含义)分类问题就是将篇文档
  • jQuery插件markitup轻量文本编辑器

    千次阅读 2015-11-11 14:19:15
    jQuery Universal Markup Editor是个基于jQuery的轻量文本编辑器,可实现非常强大的在线文本编辑器功能。可支持html、Wiki、BBScode等编辑格式,具体很强的扩展性,使用非常方便,markitup效果图如下: 使用...
  • 本文提出了种新的深度双循环编码器模型,该模型同时利用文本数据音频信号来更好地理解语音数据。由于情感对话是由声音口语内容组成的,因此我们的模型使用双循环神经网络(RNN)对音频和文本序列中的信息进行...
  • Notepad2 是个相当优秀的轻量级文本编辑器。Notepad2 是基于 Scintilla 开发,体积小巧、运行快速,与系统默认记事本具备相同资源消耗,但提供了大量实用功能,如代码高亮、编码转换、行号显示、多步 Ctrl+Z、增强...
  • 文本地址智能识别组件()

    万次阅读 2020-03-10 11:33:38
    达到数据库实时同步,并且自动补全的功能,当我们地址里面输入湖南省岳麓区时自动匹配长沙市,但输入长沙市岳麓区时,自动匹配出湖南省,达到实际需求中真正的智能识别文本地址信息 方案 大概的思路是数据库中存在想...
  • 最近公司有个需求,要求要对2800...这样做,对于少量数据可以用,但是千万数据的笛卡尔积或阶乘式的匹配带来的计算量实在太恐怖了,在spark 上跑,一会就full GC了,或是报数组长度超过java允许的最大长度的错误。
  • 这与实际情况是不相符的,为了解决这个问题,可以采用文本的分布式表示方式(例如 word embedding形式),通过文本的分布式表示,把文本表示成类似图像语音的连续、稠密的数据。这样我们就可以把深度学习方法迁移到...
  • 套习题 1 . 题干 请在【答题】菜单下选择【进入考生文件夹】命令,并按照题目要求完成下面的操作。 ​ 在考生文件夹下打开文档 ( Word素材.docx ) ,按照要求完成下列操作并以该文件名( Word.docx )保存文档。...
  • Sed 流文本编辑器

    万次阅读 2018-12-10 16:27:21
    )Sed流文本编辑器介绍  sed (Stream EDitor) 本质上是个编辑器,但是它是非交互式的,这点与VIM不同;同时它又是面向字符流的,输入的字符流经过sed的处理后输出。这两个特性使得sed成为命令行下面非常有用...
  • WPF教程(文本呈现

    千次阅读 2016-10-20 21:41:46
    在本章节,我们讨论为什么有时候在WPF中文本呈现得很模糊,之后是如何修复的,你自己又如何控制文本的呈现。 前面的教程我们有提到,其他UI框架如WinForms通过使用Windows API来实现各种功能,相对于这些框架,WPF...
  • 当你输好第一级标题之后按 tab键就可以进入第二级的标题设置,第三级标题同理1、 一级标题: 第1步,打开Word2007文档窗口,在“开始”功能区的“样式”分组中单击显示样式窗口按钮。 2、 二级标题: 第2步,在打开...
  • 首先我们来说说文本传输与进制传输的...如果个被传输对象是utf8格式的字符串,那么无论是文本传输还是进制传输给客户端,客户端解析也必须使用utf8进行decode来获得该字符串。   不同点:<br /

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 441,388
精华内容 176,555
关键字:

一级文本和二级文本