Storm 0.9.4 to Storm 1.0.1升级指南

  1. 升级pom文件依赖至storm-core 1.0.1
  2. 升级pom文件依赖至storm-kafka 1.0.1
  3. 替换所有的backtype至org.apache
  4. 替换所有的storm.kafka至org.apache.storm.kafka
  5. 替换所有的storm.trident至org.apache.storm.trident
  6. 替换所有的org.apache.thrift7至org.apache.storm.thrift
  7. 使用storm-kafka时,用到SpoutConfig的forceFromStart替换为ignoreZkOffsets
  8. StormSubmitter.submitTopology方法新增一个抛出异常AuthorizationException,处理即可。
  9. Storm-kafka依赖0.9.0.1的kafka-clients,如需使用0.8.1的kafka集群,请在pom文件中排除kafka-clients依赖,并重新加入正确版本,如下:

<dependency>
    <groupId>org.apache.storm</groupId>
    <artifactId>storm-kafka</artifactId>
    <version>1.0.1</version>
        <exclusions>
            <exclusion>
                <artifactId>kafka-clients</artifactId>
                <groupId>org.apache.kafka</groupId>
            </exclusion>
        </exclusions>
</dependency>

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_2.10</artifactId>
    <version>0.8.1.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>

</dependency>

如果所使用的kafka依赖版本里没有对kafka-clients的依赖,请再加入该依赖(storm-kafka依赖于kafka-clients)

其他已知改变:

1.Storm-kafka中所有的KafkaBolt(包括Trident和普通),不再使用配置”kafka.broker.properties”或xxBolt.KAFKA_BROKER_PROPERTIES ( 也即是 ”kafka.broker.properties” )来传递Kafka Producer的配置,而是使用withProducerProperties方法传递该配置(TridentKafkaState使用TridentKafkaStateFactory的该方法实现)。必要的三个配置为:bootstrap.servers,key.serializer,value.serializer。

2.org.apache.storm.tuple.Tuple类中的方法变更:已下方法被弃用,

@Deprecated

public GlobalStreamId getSourceGlobalStreamid();

新增方法:

public GlobalStreamId getSourceGlobalStreamId();

故代码升级时,需要Tuple的子类需要实现该方法。

3.由于storm 1.0.1的lib中删除了logback的包,故项目中用到的logback的,需要在pom文件中增加依赖。

    <dependency>

           <groupId>ch.qos.logback</groupId>

           <artifactId>logback-classic</artifactId>

           <version>1.1.6</version>

   </dependency>

4.org.apache.storm.spout.Scheme 中的函数List<Object> deserialize(ByteBuffer ser)的参数由byte[]转换为ByteBuffer。使用byte[] ser =org.apache.storm.utils.Utils.toByteArray(string)做转换

可能出现的问题:

  1. ERROR o.a.k.c.p.i.Sender – Uncaught error in kafka producer I/O thread: org.apache.kafka.common.protocol.types.SchemaException: Error reading field 'throttle_time_ms': java.nio.BufferUnderflowException: kafka-clients版本不对,用0.9的clients往0.8的kafka上写数据就会有这个问题,排除方式见上。

注意事项:

  1. 由于storm 1.0中删除了logback的依赖,改用log4j2,故项目中用到的logback的,需要在pom文件中增加依赖。但增加了logback依赖后,slf4j会检测到多个底层log库,抛出Multiple Binding 警告,可能出现日志配置失效的情况。建议所有应用方使用slf4j,不要绕过slf4j直接使用底层日志库。http://www.slf4j.org/manual.html

Worker实例化Spout Bolt过程分析

以下代码基于storm1.0.1
首先我们知道,执行一个Topology时,Supervisor将从Nimbus上下载三个文件,分别是stormconf.ser,stormcoe.ser和stormjar.jar。其中stormcore.ser即是该topology的序列化文件。Worker会从supervisor目录下读取该文件。时序图:

同时,在get-task-object中还有shellBolt和JavaObject两种类型。这两种类型都是提供给NONE-JVM DML使用的。其中的JavaObject是Thrift对象,供其他语言调用原生的Java对象使用(如在python中调用Java编写的spout)。其本质与序列化的Java对象不同。

博客荒废了很久准备重新开始写起来。

最近一直在忙毕设的事情准备用深度学习的方法进行微博情感分析,在我们的研究中,将使用5分类的方法来将微博进行分类。
之前纠结深度学习工具的选择,先后在theano,deeplearningtoolbox,torch和deeplarining4j之间纠结了很久,选来选去最终还是选了torch7。具体原因先按下不表,过程很纠结就是了。

torch7除了标准的nn包之外,还提供了dp包来进行深度学习,本文就利用了dp包进行编程。处理了固定长度(12词)的文本分类问题。不同长度的代码还在编写中。

 

  • 数据准备

数据准备方面,首先是用了word2vec工具将分词后的文本都学习成了embeded vector每个词向量长度100,将词长度为12的句子挑选出来进行采样,制作成训练集、验证集合测试集,相当于每个句子是一个1200维的向量。五个文件,分别代表5个分类。

在读取文件的时候,我把1×1200维的向量reshape成了12*100维的向量。
prepareData.lua

[code]
for _,dataset_name in ipairs({"train","valid","test"}) do
datas=nil
classes=nil
path_prefix=os.getenv(‘HOME’).."/data/weibo/"
th_output_prefix=os.getenv(‘HOME’).."/workspace/torch7/"
path_surfix=".txt"
for _,index in ipairs({0,1,2,3,4}) do
data_n={}
classes_n={}
file=io.open(path_prefix..dataset_name..index..path_surfix,’r’)
for line in file:lines() do
line_vector={}
for element in string.gmatch(line,"%S+") do
table.insert(line_vector,element)
end
table.insert(data_n,line_vector)
end
data_tensor_n=torch.Tensor(data_n)
data_tensor_n=data_tensor_n:resize(data_tensor_n:size(1),data_tensor_n:size(2)/100,100)
classes_tensor_n=torch.Tensor(data_tensor_n:size(1)):fill(index)
print(data_tensor_n:size())
print(classes_tensor_n:size())
datas=datas and torch.cat(datas,data_tensor_n,1) or data_tensor_n
classes=classes and torch.cat(classes,classes_tensor_n,1) or classes_tensor_n
end
classes=classes:int()
print(datas:size())
print(classes:size())
data_object={datas,classes}
torch.save(th_output_prefix..dataset_name..’.th7′,data_object)
end
[/code]

制作3个数据文件,分别取名为train.th7,valid.th7和test.th7。

  • datasource编写
    利用mnist的datasource改的。注意,我们需要的输入是一个SequenceView,也就是可以用来做1维卷积的View。SequenceView中的bwc分别代表“batch大小”,“句子长度”和“embedVector的大小”

[code]
local Weibo, DataSource = torch.class("dp.Weibo", "dp.DataSource")
Weibo.isMnist = true

Weibo._name = ‘weibo’
Weibo._text_axes = ‘bwc’
Weibo._classes = {0, 1, 2, 3, 4, }

function Weibo:__init(config)
config = config or {}
assert(torch.type(config) == ‘table’ and not config[1],
"Constructor requires key-value arguments")
local args, load_all, input_preprocess, target_preprocess
args, self._valid_ratio, self._train_file, self._test_file, self._valid_file,
self._data_path, self._scale, self._binarize, self._shuffle, load_all, input_preprocess,
target_preprocess
= xlua.unpack(
{config},
‘Weibo’,
‘Handwritten digit classification problem.’ ..
‘Note: Train and valid sets are already shuffled.’,
{arg=’valid_ratio’, type=’number’, default=1/6,
help=’proportion of training set to use for cross-validation.’},
{arg=’train_file’, type=’string’, default=’train.th7′,
help=’name of training file’},
{arg=’valid_file’, type=’string’, default=’valid.th7′,
help=’name of valid file’},
{arg=’test_file’, type=’string’, default=’test.th7′,
help=’name of test file’},
{arg=’data_path’, type=’string’, default=dp.DATA_DIR,
help=’path to data repository’},
{arg=’scale’, type=’table’,
help=’bounds to scale the values between. [Default={0,1}]’},
{arg=’binarize’, type=’boolean’,
help=’binarize the inputs (0s and 1s)’, default=false},
{arg=’shuffle’, type=’boolean’,
help=’shuffle different sets’, default=false},
{arg=’load_all’, type=’boolean’,
help=’Load all datasets : train, valid, test.’, default=true},
{arg=’input_preprocess’, type=’table | dp.Preprocess’,
help=’to be performed on set inputs, measuring statistics ‘ ..
‘(fitting) on the train_set only, and reusing these to ‘ ..
‘preprocess the valid_set and test_set.’},
{arg=’target_preprocess’, type=’table | dp.Preprocess’,
help=’to be performed on set targets, measuring statistics ‘ ..
‘(fitting) on the train_set only, and reusing these to ‘ ..
‘preprocess the valid_set and test_set.’}
)
self:loadTrain()
self:loadValid()
self:loadTest()
DataSource.__init(self, {
train_set=self:trainSet(), valid_set=self:validSet(),
test_set=self:testSet(), input_preprocess=input_preprocess,
target_preprocess=target_preprocess
})
end

function Weibo:loadTrain()
local train_data = self:loadData(self._train_file)
self:setTrainSet(
self:createDataSet(train_data[1], train_data[2], ‘train’)
)
return self:trainSet()
end

function Weibo:loadValid()
local valid_data = self:loadData(self._valid_file)
self:setValidSet(
self:createDataSet(valid_data[1], valid_data[2], ‘valid’)
)
return self:validSet()
end

function Weibo:loadTest()
local test_data = self:loadData(self._test_file)
self:setTestSet(
self:createDataSet(test_data[1], test_data[2], ‘test’)
)
return self:testSet()
end

function Weibo:createDataSet(inputs, targets, which_set)
if self._shuffle then
local indices = torch.randperm(inputs:size(1)):long()
inputs = inputs:index(1, indices)
targets = targets:index(1, indices)
end
if self._binarize then
DataSource.binarize(inputs, 128)
end
— class 0 will have index 1, class 1 index 2, and so on.
targets:add(1)
— construct inputs and targets dp.Views
local input_v, target_v = dp.SequenceView(), dp.ClassView()
input_v:forward(self._text_axes, inputs)
target_v:forward(‘b’, targets)
target_v:setClasses(self._classes)
— construct dataset
dataset= dp.DataSet{inputs=input_v,targets=target_v,which_set=which_set}
–print(dataset)
return dataset
end

function Weibo:loadData(file_name)
local path="../"..file_name
print(file_name)
— backwards compatible with old binary format
local status, data = pcall(function() return torch.load(path) end)
if not status then
return torch.load(path, "binary")
end
return data
end
[/code]

  • 实验代码编写
    使用cnn的方式处理,分为三层,第一层是一个一维卷积,第二层和第三层都是传统的神经网络写法。

[code]
require ‘dp’
require ‘weiboSource’

–[[hyperparameters]]–
opt = {
nHidden = 100, –number of hidden units
learningRate = 0.1, –training learning rate
momentum = 0.9, –momentum factor to use for training
maxOutNorm = 1, –maximum norm allowed for output neuron weights
batchSize = 128, –number of examples per mini-batch
maxTries = 100, –maximum number of epochs without reduction in validation error.
maxEpoch = 1000, –maximum number of epochs of training
cuda =false,
useDevice =1,
inputEmbeddingSize =100,
outputEmbeddingSize=100,

convOutputSize=50,
convKernelSize=2,
convKernelStride=1,
convPoolSize=2,
convPoolStride=2,
contextSize=4,
decayPoint=100 ,–epoch at which learning rate is decayed
decayFactor=0.1, –‘factory by which learning rate is decayed at decay point’
}

local datasource=dp.Weibo()

inputModel = dp.Convolution1D{
input_size = opt.inputEmbeddingSize,
output_size = opt.convOutputSize,
kernel_size = opt.convKernelSize,
kernel_stride = opt.convKernelStride,
pool_size = opt.convPoolSize,
pool_stride = opt.convPoolStride,
transfer = nn.Tanh(),
dropout = opt.dropout and nn.Dropout() or nil,
acc_update = opt.accUpdate
}
local nOutputFrame = inputModel:outputSize(opt.contextSize, ‘bwc’)
dp.vprint(not opt.silent, "Convolution has "..nOutputFrame.." output Frames")
inputSize = nOutputFrame*opt.convOutputSize
–print(hiddenModel)

softmax = dp.Neural{
input_size = opt.outputEmbeddingSize,
output_size = table.length(datasource:classes()),
transfer = nn.LogSoftMax(),
dropout = opt.dropout and nn.Dropout() or nil,
acc_update = opt.accUpdate
}

mlp = dp.Sequential{
models = {
inputModel,
dp.Neural{
input_size = inputSize,
output_size = opt.outputEmbeddingSize,
transfer = nn.Tanh(),
dropout = opt.dropout and nn.Dropout() or nil,
acc_update = opt.accUpdate
}
}
}

–[[Propagators]]–
train = dp.Optimizer{
loss = opt.softmaxtree and dp.TreeNLL() or dp.NLL(),
visitor = {
dp.Learn{
learning_rate = opt.learningRate,
observer = dp.LearningRateSchedule{
schedule = {[opt.decayPoint]=opt.learningRate*opt.decayFactor}
}
},
dp.MaxNorm{max_out_norm=opt.maxOutNorm, period=opt.maxNormPeriod}
},
feedback = dp.Perplexity(),
sampler = dp.Sampler{ –shuffle sample takes too much mem
epoch_size = opt.trainEpochSize, batch_size = opt.batchSize
},
progress = opt.progress
}
valid = dp.Evaluator{
loss = opt.softmaxtree and dp.TreeNLL() or dp.NLL(),
feedback = dp.Perplexity(),
sampler = dp.Sampler{
epoch_size = opt.validEpochSize,
batch_size = opt.softmaxtree and 1024 or opt.batchSize
},
progress = opt.progress
}
tester = dp.Evaluator{
loss = opt.softmaxtree and dp.TreeNLL() or dp.NLL(),
feedback = dp.Perplexity(),
sampler = dp.Sampler{batch_size = opt.softmaxtree and 1024 or opt.batchSize}
}

–[[Experiment]]–
xp = dp.Experiment{
model = mlp,
optimizer = train,
validator = valid,
tester = tester,
observer = (not opt.trainOnly) and {
dp.FileLogger(),
dp.EarlyStopper{max_epochs = opt.maxTries}
} or nil,
random_seed = os.time(),
max_epoch = opt.maxEpoch
}
–[[GPU or CPU]]–
if opt.cuda then
require ‘cutorch’
require ‘cunn’
if opt.softmaxtree or opt.softmaxforest then
require ‘cunnx’
end
cutorch.setDevice(opt.useDevice)
xp:cuda()
end

print"dp.Models :"
print(mlp)
print"nn.Modules :"
trainset=datasource:trainSet():sub(1,32)

print(mlp:toModule(datasource:trainSet():sub(1,32)))

xp:run(datasource)
[/code]

  • 实验结果
    实验结果在测试集上5分类达到了70%+,令我感到十分意外,真是意外之喜

先上代码,说明后面再加。

[python]
#-*- coding:utf-8 -*-
”’
Created on 2014-04-28

@author: Howard
”’
from os import path, sys

sys.path.append("../")
from nlpir import seg
import numpy
import cPickle, codecs, os

class NaiveBayes:
def __init__(self):
self.segmentor = seg.Seg()

def createVocabList(self, dataSet):
"创建一个词表"
#❶ 创建一个空集
self.vocabSet = set([])
for document in dataSet:
#❷ 创建两个集合的并集
self.vocabSet = self.vocabSet | set(document)
self.vocabList = list(self.vocabSet)

”’
def addATrainDoc(self,doc):
self.vocabSet=self.vocabSet|set(doc)
self.vocabList=list(self.vocabSet)
”’

def bagOfWords2VecMN(self, inputSet):
#向量的每个位置上保存的是该位置词出现的个数,即向量各个分量之和等于文档总词数。
returnVec = [0] * len(self.vocabList)
for word in inputSet:
if word in self.vocabList:
returnVec[self.vocabList.index(word)] += 1
return returnVec

def trainNB0(self, trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)

#保存总文档数,扩展时使用
self.documentNum = numTrainDocs

numWords = len(trainMatrix[0])
#所有垃圾的正好是1,所以正好是加起来的数除以总分类数
p1 = sum(trainCategory) / float(numTrainDocs)
#❶ (以下两行)初始化概率 ,加入平滑(加一平滑)
p0Num = numpy.ones(numWords);
p1Num = numpy.ones(numWords)
p0Denom = 0.0;
p1Denom = 0.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
#❷(以下两行)向量相加
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])

#保存各个类型文档的数目,扩展时使用
self.p1wordNum = p1Denom
self.p0wordNum = p0Denom

p1Vect = numpy.log(p1Num / (p1Denom + len(self.vocabList))) #加一平滑的分母为 总单词数+|V|
p0Vect = numpy.log(p0Num / (p0Denom + len(self.vocabList))) #加一平滑的分母为 总单词数+|V|
#self.p1VectDict = dict(zip(self.vocabList, p1Vect)) #1类中所有单词的概率值
#self.p0VectDict = dict(zip(self.vocabList, p0Vect)) #0类中所有单词的概率值
self.vectDict = {}
for i in range(len(self.vocabList)):
self.vectDict[self.vocabList[i]] = {‘p1’: p1Vect[i], ‘p0’: p0Vect[i]}
self.p1 = p1
self.p0 = 1 – p1
return p0Vect, p1Vect, p1

def classifyNB(self, words):
"""
vec2Classify 词向量
p0Vec 在0类中的概率向量
p1Vec 在1类中的概率向量
pClass1 1类的概率
"""
#❶ 元素相乘
p1 = numpy.log(self.p1)
p0 = numpy.log(self.p0)
v1Values = [self.vectDict.get(word, {‘p1’: numpy.log(1.0 / (self.p1wordNum + len(self.vectDict)))})[‘p1’] for
word in
words] #加一平滑中未见词的概率为 总单词数+|V|
v0Values = [self.vectDict.get(word, {‘p0’: numpy.log(1.0 / (self.p0wordNum + len(self.vectDict)))})[‘p0’] for
word in words] #加一平滑中未见词的概率为 总单词数+|V|
p1 += numpy.sum(v1Values)
p0 += numpy.sum(v0Values)
if p1 > p0:
return 1, p1, p0
else:
return 0, p1, p0

def _calcMostFreq(self, fullText):
import operator

freqDict = {}
for token in self.vocabList:
freqDict[token] = fullText.count(token)
sortedFreq = sorted(freqDict.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedFreq[:30]

def saveModel(self, path):
outputfile = open(path, "w")
cPickle.dump(self.p0, outputfile)
cPickle.dump(self.p1, outputfile)
cPickle.dump(self.vectDict, outputfile)
cPickle.dump(self.documentNum, outputfile)
cPickle.dump(self.p1wordNum, outputfile)
cPickle.dump(self.p0wordNum, outputfile)
outputfile.close()

def loadModel(self, path):
if os.path.isfile(path):
inputfile = open(path, ‘r’)
self.p0 = cPickle.load(inputfile)
self.p1 = cPickle.load(inputfile)
self.vectDict = cPickle.load(inputfile)
self.documentNum = cPickle.load(inputfile)
self.p1wordNum = cPickle.load(inputfile)
self.p0wordNum = cPickle.load(inputfile)
inputfile.close()
return True
else:
return False

def increase(self, text, lable):
”’
贝叶斯增量学习
”’
words = self.segmentor.seg(text).split()
wordslength = len(words)
if lable == 1:
p1wordslength = wordslength
p0wordslength = 0
else:
p1wordslength = 0
p0wordslength = wordslength
#更新不同标记的分类的频率 步骤一
self.p0 = self.documentNum * self.p0 / (self.documentNum + 1)
self.p1 = self.documentNum * self.p1 / (self.documentNum + 1)

newwordscount = 0
for word in words:
if not self.vectDict.has_key(word):
newwordscount += 1
dictlength = len(self.vectDict)

def updateFrequency1(key):
self.vectDict[key][‘p1’] += numpy.log(
(self.p1wordNum + dictlength) / (self.p1wordNum + (dictlength + newwordscount) + p1wordslength))

def updateFrequency0(key):
self.vectDict[key][‘p0’] += numpy.log(
(self.p0wordNum + dictlength) / (self.p0wordNum + (dictlength + newwordscount) + p0wordslength))

if lable == 1:
self.p1 += 1.0 / (self.documentNum + 1) #更新不同标记的分类的频率 步骤二
#更新该分类下所有词的频率
map(updateFrequency1,filter((lambda key: not words.__contains__(key)),self.vectDict.keys()))
if newwordscount != 0: #如果新词频率不等于零,还要更新对侧分类的频率值
map(updateFrequency0, self.vectDict.keys())

#更新出现了的词的频率
for word in set(words):
if self.vectDict.has_key(word):
self.vectDict[word][‘p1’] = numpy.log(
((self.p1wordNum + dictlength) * numpy.exp(self.vectDict[word][‘p1’]) + words.count(word)) / (
self.p1wordNum + (dictlength + newwordscount) + p1wordslength))
else:
self.vectDict[word] = {}
self.vectDict[word][‘p1’] = numpy.log((words.count(word) + 1.0) / (
self.p1wordNum + (dictlength + newwordscount) + p1wordslength)) #+1是为了平滑,与最初构建时的加一平滑一致
self.vectDict[word][‘p0’] = numpy.log(
1.0 / (self.p0wordNum + (dictlength + newwordscount) + p0wordslength))
self.p1wordNum += wordslength

else:
self.p0 += 1.0 / (self.documentNum + 1) #更新不同标记的分类的频率 步骤二
#更新该分类下所有词的频率
map(updateFrequency0, filter((lambda key: not words.__contains__(key)),self.vectDict.keys()))
if newwordscount != 0: #如果新词频率不等于零,还要更新对侧分类的频率值
map(updateFrequency1, self.vectDict.keys())

#更新出现了的词的频率
for word in set(words):
if self.vectDict.has_key(word):
self.vectDict[word][‘p0’] = numpy.log(
((self.p0wordNum + dictlength) * numpy.exp(self.vectDict[word][‘p0’]) + words.count(word)) / (
self.p0wordNum + (dictlength + newwordscount) + p0wordslength))
else:
self.vectDict[word] = {}
self.vectDict[word][‘p0’] = numpy.log((words.count(word) + 1.0) / (
self.p0wordNum + (dictlength + newwordscount) + p0wordslength)) #+1是为了平滑,与最初构建时的加一平滑一致
self.vectDict[word][‘p1′] = numpy.log(
1.0 / (self.p1wordNum + (dictlength + newwordscount) + p1wordslength))
self.p0wordNum += wordslength
self.documentNum += 1

def train(self, texts, lables):
wordMatrix = []
fullText = []
for text in texts:
words = self.segmentor.seg(text).split()
wordMatrix.append(words)
fullText.extend(words)
self.createVocabList(wordMatrix)
”’
top30Words = self._calcMostFreq(fullText)
for pairW in top30Words:
if pairW[0] in self.vocabList: self.vocabList.remove(pairW[0])
”’
trainMatrix = []
for docs in wordMatrix:
trainMatrix.append(self.bagOfWords2VecMN(docs))
self.trainNB0(numpy.array(trainMatrix), numpy.array(lables))

def classify(self, text):
words = self.segmentor.seg(text).split()
return self.classifyNB(words)

if __name__ == ‘__main__’:
texts = [u’i am spam 1′, u’i am not spam 2′, u’i am spam 2′, ‘i am not spam 3′]
#texts = [u’i am spam 1′, u’i am not spam 1’, u”, ”]
lables = [1, 0, 1, 0]

bayes1 = NaiveBayes()
bayes1.train(texts, lables)
print ‘testing spam’
psum = 0
for (k, v) in bayes1.vectDict.items():
psum += numpy.exp(v[‘p1’])
print k, v[‘p1’]
print psum
print ‘testing common’
psum = 0
for (k, v) in bayes1.vectDict.items():
psum += numpy.exp(v[‘p0’])
print k, v[‘p0′]
print psum
bayes2 = NaiveBayes()
bayes2.train(texts[:2], lables[:2])
bayes2.increase(u’i am spam 2′, 1)
bayes2.increase(u’i am not spam 3’, 0)
#bayes2.increase(u”, 1)
#bayes2.increase(u”, 0)
print ‘testing spam’
psum = 0
for (k, v) in bayes2.vectDict.items():
psum += numpy.exp(v[‘p1’])
print k, v[‘p1’]
print psum
print ‘testing common’
psum = 0
for (k, v) in bayes2.vectDict.items():
psum += numpy.exp(v[‘p0’])
print k, v[‘p0’]
print psum
[/python]

参考文献:http://www.ituring.com.cn/article/32338

最近又要做一个微信公众平台项目了。
距离前一次做项目已经过去了一年的时间,在这一年的时间里面微信公众平台的变化很大啊!添加了很多功能,再使用简单的web.py开发起来就有一些难度了(也不是难度,就是工作量问题)。果断开始寻找python版的微信框架。

于是我找到了WeRobot框架,框架的开发者仍然是一名高中生,但不妨碍这是一个很好用的框架。截止这篇文章的时候版本到了0.53,微信平台截止目前所有的功能都支持。而且这个框架甚至提供了在SAE上部署的教程。甚为方便。

直接点开该项目的sae-demo,照着其示例代码进行werobot安装,在此之前你需要git和virtualenv,这两者的安装不赘述。WeRobot SAE安装代码如下:

[code]
git clone git://github.com/whtsky/WeRoBot-SAE-demo.git
cd WeRoBot-SAE-demo
virtualenv –no-site-packages .
source bin/activate #如果是windows环境请使用Scripts\activate代替
pip install sae-python-dev
saecloud install werobot
[/code]

注意作者在这个地方的代码有误,bin/active应该为上面的bin/activate,windows环境下的代码也不相同。
如果没有什么错误的话,本地代码就应该创建好了现在去修改config.yaml文件中的name属性,再使用

[code]
saecloud deploy .
[/code]

命令就可以上传了,需要注意的是一定要记得在本地先安装svn再使用saecloud deploy,不然肯定会遇到error2,我找这个问题找了好久,读了源代码才发现,结果sae的官方文档里面就有,怪我读文档不仔细。

成功之后还是发现index.wsgi,有错误,检查多遍才发现仍然是找不到lib路径的问题,除了官方示例中robot.py中的sys.path.insert(0, os.path.join(root, ‘site-packages’))之外,还需要加一句:sys.path.insert(1, os.path.join(root, ‘Lib/site-packages’)),robot.py的完整代码如下:

[python]
import os
import sys

root = os.path.dirname(__file__)

sys.path.insert(0, os.path.join(root, ‘site-packages’))
sys.path.insert(1, os.path.join(root, ‘Lib/site-packages’))

import werobot

robot = werobot.WeRoBot(token=’tokenhere’)

@robot.handler
def echo(message):
return ‘Hello World!’
[/python]

此时访问主页仍然出错,查看原因后发现,与文档中写的不一样,0.53版本的session是默认开启的,sae又不允许文件io,所以出错,修改办法是在创建WeRobot实例时再传入参数enable_session=False。
如果想要使用session,可以使用sae的kvdb来保存session信息,首先在sae的控制面板中开启kvdb,然后将如下代码保存为site-packages/werobot/session/kvdbstorage.py

[python]
# -*- coding: utf-8 -*-
”’
Author: Howard
Page: http://www.nilday.com
”’

from werobot.session import SessionStorage
from werobot.utils import json_loads, json_dumps
import sae.kvdb

class KVDBStorage(SessionStorage):
"""
SAE KVDB Storage
需要开启SAE的KVDB服务
"""
def __init__(self):
self.kv = sae.kvdb.KVClient()

def get(self, id):
stringvalue=self.kv.get(id)
if stringvalue==None:
return {}
value=json_loads(stringvalue)
return value

def set(self, id, value):
session = json_dumps(value)
self.kv.set(id,session)

def delete(self, id):
self.kv.delete(id)
[/python]

在robot.py文件中修改如下:

[python]
import os
import sys

root = os.path.dirname(__file__)

sys.path.insert(0, os.path.join(root, ‘site-packages’))
sys.path.insert(1, os.path.join(root, ‘Lib/site-packages’))

import werobot
from werobot.session.kvdbstorage import KVDBStorage

session_storage=KVDBStorage()
robot = werobot.WeRoBot(token=’tokenhere’,enable_session=True,session_storage=session_storage)

@robot.handler
def hello(message, session):
count = session.get("count", 0) + 1
session["count"] = count
return "Hello! You have sent %s messages to me" % count
[/python]

到此为止我们的微信公众平台就搭建好了,大家可以把token换成自己的token,此时直接访问主要会得到一个403错误页面这是由于微信公众平台不允许直接get方式访问导致,在微信公众平台的控制面板上设置好调用地址和token,就可以使用了。
以上提到的一些bug都已经反馈给了开发者,希望他越做越好,真是后生可畏啊!

参考文献:

https://werobot.readthedocs.org/en/latest/

https://github.com/whtsky/WeRoBot-SAE-demo

http://sae.sina.com.cn/doc/python/tools.html#saecloud

上一篇文章:利用SAE搭建微信公众平台(二)

(1)      使用JNI对CRF++进行封装

由于CRF++是由C++语言编写的条件随机场工具,为了在系统主体的Java程序中使用,需要使用JNI技术对Java进行扩展,并将CRF++编译为可被Java加载和调用的动态链接库。

JNI是Java Native Interface的缩写,中文为JAVA本地调用,它允许Java代码和其他语言写的代码进行交互。在没有SWIG工具时,需要手工编写大量代码用于将C++代码封装为可供Java调用的接口函数,在SWIG工具的帮助下,能大量减少工作。

SWIG是个帮助使用C或者C++编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。在本系统中,我们使用SWIG将C++编写的CRF++程序与Java编写的系统主要部分联接。

下载SWIG Windows版,解压到某一安装目录下,将含有swig.exe的目录添加的环境变量Path中,供后续使用。

SWIG对程序的封装需要编写接口文件,在接口文件中导入“crfpp.h”文件,使得SWIG导出crfpp.h中定义的接口,将其封装为JNI。编写接口文件CRFPP.i如下:

[code]
%module CRFPP
%include exception.i
%{
#include "crfpp.h"
%}
%newobject surface;
%exception {
try { $action }
catch (char *e) { SWIG_exception (SWIG_RuntimeError, e); }
catch (const char *e) { SWIG_exception (SWIG_RuntimeError, (char*)e); }
}
%feature("notabstract") CRFPP::Tagger;
%ignore CRFPP::createTagger;
%ignore CRFPP::getTaggerError;
%extend CRFPP::Tagger { Tagger(const char *argc); }
%{
void delete_CRFPP_Tagger (CRFPP::Tagger *t) {
delete t;
t = 0;
}
CRFPP::Tagger* new_CRFPP_Tagger (const char *arg) {
CRFPP::Tagger *tagger = CRFPP::createTagger(arg);
if (! tagger) throw CRFPP::getTaggerError();
return tagger;
}
%}
%include crfpp.h
%include version.h
[/code]

其中crfpp.h与version.h文件都可以在CRF++源代码中找到,但需将crfpp.h替换为前文中提到的64位的头文件。

进入Windows命令行界面,切换至含有CRFPP.i接口文件的路径,输入命令:

[code]swig –c++ -java -package org.chasen.crfpp CRFPP.i[/code]

命令执行完后会生成如表生成的CRF++封装文件所示的6个文件。

生成的CRF++封装文件

CRFPP.java
CRFPPConstants.java
CRFPPJNI.java
SWIGTYPE_p_unsigned___int64.java
Tagger.java
CRFPP_wrap.cxx

其中,CRFPP_wrap.cxx文件是C++端的封装文件用于将CRF++封装为JNI接口。将其拷贝至CRF++编译工程目录。

将另外5个Java文件拷贝至本系统主体Java工程的org.chasen.crfpp包中,其中Tagger类是我们将要使用到的主要类。

(2)      JNI中文乱码的解决

由于在java内部是使用的16bit的Unicode编码(UTF-16)来表示字符串的,无论英文还是中文都是2字节;JNI内部是使用UTF -8编码来表示字符串的,UTF -8是变长编码的Unicode,一般ASCII字符是1字节,中文是3字节;C/C++使用的是原始数据,ASCII就是一个字节,在Windows操作系统中,中文一般是GB2312编码,用2个字节表示一个汉字。[21]如果直接在Java程序中传入中文字符串将会出现不可逆的中文乱码,对命名实体识别的效果有很大影响。

在swig生成的CRFPP_wrap.cxx文件中,在约270行的位置找到“#include “crfpp.h””,在这条语句下面加入两个函数:

l  JStringToWindows

[cpp]
//to use chinese never foget to free.
char * JStringToWindows(JNIEnv * pJNIEnv, jstring jstr)
{
jsize len = pJNIEnv-&gt;GetStringLength(jstr);
const jchar * jcstr = pJNIEnv-&gt;GetStringChars(jstr, NULL);
int size = 0;
char * str = (char *)malloc(len * 2 + 1);
if ((size = WideCharToMultiByte(CP_ACP, 0, LPCWSTR(jcstr), len, str, len * 2 + 1, NULL, NULL)) == 0)
return NULL;
pJNIEnv-&gt;ReleaseStringChars(jstr, jcstr);
str[size] = 0;
return str;
}
[/cpp]
l  WindowsTojstring

[cpp]
jstring WindowsTojstring( JNIEnv* env, char* str )
{
jstring rtn = 0;
int slen = strlen(str);
unsigned short * buffer = 0;
if( slen == 0 )
rtn = (env)-&gt;NewStringUTF(str );
else
{
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
buffer = (unsigned short *)malloc( length*2 + 1 );
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) &gt;0 )
rtn = (env)-&gt;NewString(  (jchar*)buffer, length );
}
if( buffer )
free( buffer );
return rtn;
}
[/cpp]

在源文件中查找“GetStringUTFChars”函数,将其替换为“JStringToWindows”,查找“NewStringUTF”,将其替换为“WindowsTojstring”。并在“JStringToWindows”函数返回的字符串使用后,使用stdio标准库中的“free”函数将其释放,以防造成内存泄露。

在CRF++工程中,以动态链接库形式编译CRFPP_wrap.cxx为CRFPP.dll文件。将该文件拷贝到本系统Java主体的工程根目录下。

(3)      JNI封装后CRF++的使用

在Tagger类中加入如下代码:

[java]
static {
try {
System.loadLibrary("CRFPP");
} catch (UnsatisfiedLinkError e) {
System.err.println("Cannot load the example native code.\nMake sure your LD_LIBRARY_PATH contains \’.\’\n"+ e);
System.exit(1);
}
}
[/java]

在Tagger类被调用的时候,会自动在环境变量的Path路径下查找CRFPP.dll动态链接库,作为CRF++的核心载入主程序中使用。

1)        初始化Tagger类时使用“-m”参数指定模型文件地址。

2)        使用boolean clear()方法清除已有的数据。

3)        使用boolean add(String str)方法添加特征,每一个add方法中添加的特征形如CRF++的训练文件,只是少了标注列。

4)        使用boolean parse()方法出发CRF++的标注。

5)        使用String y2(long i)方法获取对应序号CRF++标注。

上篇文章讲了如何在SAE搭建Hello World,那么这篇文章主要写如何实现微信的验证和自动回复。

首先修改SAE中的index.wsgi文件如下:

[python]
# coding: UTF-8
import os

import sae
import web

from weixinInterface import WeixinInterface

urls = (
‘/’, ‘Hello’,
‘/weixin’,’WeixinInterface’
)

app_root = os.path.dirname(__file__)
templates_root = os.path.join(app_root, ‘templates’)
render = web.template.render(templates_root)

class Hello:
def GET(self):
#print "你好"
return render.hello("你好")

app = web.application(urls, globals()).wsgifunc()

application = sae.create_wsgi_app(app)
[/python]

即创建一个weixinInterface类来处理微信的请求。
在目录下创建weixinInterface文件,定义WeixinInterface类。

一、实现微信验证。

回顾微信开发者的验证方式(一下摘自微信官方文档):

填写信息

公众平台用户提交信息后,微信服务器将发送GET请求到填写的URL上,并且带上四个参数:

参数 描述
signature 微信加密签名
timestamp 时间戳
nonce 随机数
echostr 随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,否则接入失败。

signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。

加密/校验流程:
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

那么首先要解决的就是get方法的问题。在WeixinInterface模块中添加代码如下:

[python]
#coding:UTF-8
import hashlib
import web
class WeixinInterface:

def GET(self):
#获取输入参数
data=web.input()

signature=data.signature
timestamp=data.timestamp
nonce=data.nonce
echostr=data.echostr
#自己的token
token="yourtoken"
#字典序排序
list=[token,timestamp,nonce]
list.sort()
#sha1加密算法
sha1=hashlib.sha1()
map(sha1.update,list)
hashcode=sha1.hexdigest()
#如果是来自微信的请求,则回复echostr
if hashcode == signature:
#print "true"
return echostr
[/python]

现在,放回微信公众平台的注册界面,填上你的验证地址即:http://yourdomain.sinaapp.com/weixin,token填自己的token,要和上面程序中一致。点击提交,如果没有意外现在就应该验证成功了。

二、实现简单的文本回复

现在阅读微信公众平台的文档,先只注意接收文本信息和回复文本信息的格式:

消息推送

当普通微信用户向公众账号发消息时,微信服务器将POST该消息到填写的URL上。结构如下:

文本消息

[xml]
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
[/xml]


参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType text
Content 文本消息内容
MsgId 消息id,64位整型

消息回复

对于每一个POST请求,开发者在响应包中返回特定xml结构,对该消息进行响应(现支持回复文本、图文、语音、视频、音乐和对收到的消息进行星标操作)。 微信服务器在五秒内收不到响应会断掉连接。 回复xml结构如下:

回复文本消息

[xml]
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[content]]></Content>
<FuncFlag>0</FuncFlag>
</xml>
[/xml]

参数 描述
ToUserName 接收方帐号(收到的OpenID)
FromUserName 开发者微信号
CreateTime 消息创建时间
MsgType text
Content 回复的消息内容,长度不超过2048字节
FuncFlag 位0x0001被标志时,星标刚收到的消息。

那么显然的是,我们需要解析xml文件。SAE预装了lxml库,能够方便的解析xml。首先切换到config.yaml,添加代码,效果如下:

[code]

name: tedxfactory798
version: 1
libraries:
– name: webpy
version: "0.36"
– name: lxml
version: "2.3.4"
[/code]

下面切换回weixinInterface文件,代码如下:

[python]
#coding:UTF-8
import hashlib
import web
import lxml
import time
import os
from lxml import etree
class WeixinInterface:

def __init__(self):
self.app_root = os.path.dirname(__file__)
self.templates_root = os.path.join(self.app_root, ‘templates’)
self.render = web.template.render(self.templates_root)

def GET(self):
#获取输入参数
data=web.input()

signature=data.signature
timestamp=data.timestamp
nonce=data.nonce
echostr=data.echostr
#自己的token
token="yourtoken"
#字典序排序
list=[token,timestamp,nonce]
list.sort()
#sha1加密算法
sha1=hashlib.sha1()
map(sha1.update,list)
hashcode=sha1.hexdigest()
#如果是来自微信的请求,则回复echostr
if hashcode == signature:
#print "true"
return echostr

def POST(self):
#从获取的xml构造xml dom树
str_xml=web.data()
xml=etree.fromstring(str_xml)
#提取信息
content=xml.find("Content").text
msgType=xml.find("MsgType").text
fromUser=xml.find("FromUserName").text
toUser=xml.find("ToUserName").text
#模板渲染
return self.render.reply_text(fromUser,toUser,int(time.time()),u"大家好我现在还只会卖萌,你刚才说的是:"+content)
[/python]

那么我们还需要建立一个模板,在template目录下,reply_text.xml:

[xml]
$def with (toUser,fromUser,createTime,content,funcFlag=0)
<xml>
<ToUserName><![CDATA[$toUser]]></ToUserName>
<FromUserName><![CDATA[$fromUser]]></FromUserName>
<CreateTime>$createTime</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[$content]]></Content>
<FuncFlag>$funcFlag</FuncFlag>
</xml>
[/xml]

保存全部文件,注意一下toUser和fromUser的顺序,和接受的时候正好相反。

如果没有错的话~在微信上加上自己的公众账号测试吧!

上一篇文章:利用SAE搭建微信公众平台(一)

下一篇文章:利用SAE搭建微信公众平台(三)

微信在国内已经越来越火了,特别是有了公众平台之后,出现了很多好玩的聊天机器人。比如武大助手,号称在24小时只能搭建完成。给了同学们很多方便。

最近在TEDxFactory798从事志愿者工作,负责技术部分。因为工作需要而想到了搭建TEDxFactory798的微信平台,而SAE(Sina App Engine)是国内最早开始提供云服务的平台之一,而且几乎是免费的。所以希望能够通过SAE平台快速的搭建好一个微信公众账号。在一边摸索一边学习的情况下,小半天就搭好了一个简易的微信公众平台。

首先注册微信公众账号:http://mp.weixin.qq.com,点击注册,通过很简单的几步就能快速注册好公众账号,需要注意的是这里是需要注册一个新的微信号,而不是通过已有的私人微信号绑定。可能需要注册一个专用的邮箱。注册过程很简单,不赘述。

第二步是注册SAE,访问http://sae.sina.com.cn,SAE整个的文档都比较完整,很好上手。注册完成后会送500云豆,合人民币5元。再申请实名认证,送云豆2000。对于一个刚上手的开发者来说完全够了。等应用开发好后,还可以申请开发者认证。每月有固定额度的云豆赠送,基本就不用花钱了。另外还有教育机构认证,公益组织认证等。同样十分简单,不赘述。

第三步,登录公众平台后,点击高级功能。首先要关闭编辑模式,才能进入开发模式。(可以先不关闭, 等自己的服务器假设好之后再关闭)。阅读微信公众平台文档:http://mp.weixin.qq.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5。我们看到需要提供一个接入信息:

接口配置

接口配置

那么我们需要一个网址作为接口,Token呢,就是相当于我们和微信之间约定的“密码”,以验证是微信平台的访问。再阅读接下来的要求:

网址接入

公众平台用户提交信息后,微信服务器将发送GET请求到填写的URL上,并且带上四个参数:

参数 描述
signature 微信加密签名
timestamp 时间戳
nonce 随机数
echostr 随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,否则接入失败。

signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。

加密/校验流程:
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

那么很明确我们接下来需要做的事情了:为微信提供一个接口。

第四步:在SAE上创建一个应用。在我的应用中点击创建应用:

创建应用

创建应用

填好二级域名和应用名称等,选择好语言。这里我们使用Python开发选择web应用。创建好应用之后,在代码管理中创建一个新的版本。而后我们可以选择编辑代码。能够实现在线编辑,根本用不着配置本地环境,SVN等等。当然如果你觉得需要也可以根据文档配置本地环境开发。:

代码管理

代码管理

代码编辑页

代码编辑页

那么下一步我们就要开始Hello World了。

第五步:编写Hello World。

我们这里选择web.py框架编写网页应用。该框架非常小,学习代价很低。有中文文档:http://webpy.org/cookbook/index.zh-cn。SAE平台有两个很重要的文件,一个是config.yaml,是应用的配置文件,另一个是index.wsgi,是应用的入口文件。这两个文件在创建应用的时候就已经生成了。现在打开config.yaml文件,修改为:

[code]

name: youappnapp
version: 1
libraries:
– name: webpy
version: "0.36"
[/code]

name为你的应用名,版本为1,库应用webpy,这个是SAE预装的库。预装版本为0.36。我们也就使用0.36版。修改完成后使用Ctrl+S保存,这个细节做得很好,和本地保存一样,非常方便,当然也可以使用右上角的全部保存按钮。

接下来打开index.wsgi文件,修改如下(与官方文档的hello world略有不同):

[python]
# coding: UTF-8
import os

import sae
import web

urls = (
‘/’, ‘Hello’
)

app_root = os.path.dirname(__file__)
templates_root = os.path.join(app_root, ‘templates’)
render = web.template.render(templates_root)

class Hello:
def GET(self):
return render.hello("你好")

app = web.application(urls, globals()).wsgifunc()

application = sae.create_wsgi_app(app)
[/python]

这里的coding:UTF-8告诉python文件的编码是UTF-8,对于带中文的应用来说必不可少。简单解释一下,最下面显而易见的是应用的入口,那么url配置到我们用那个类来实现路由,比如上述代码中的url是指根目录用Hello类来处理。在类中实现GET和POST方法就能处理相应的http请求。还需要注意到的是有个templates目录,在其中有个hello.html文件来实现模板。模板参数传递进一个”你好”。具体可见webpy教程。

接下来我们点击左上角的创建目录,创建一个叫做templates的目录,双击进入目录,再创建一个叫做hello.html的文件。代码如下:

[html]
$def with (name)
Hello $name!
[/html]

即定义一个name的参数(就是我们传进来的“你好”),在模板中任意地方引用$name就会被参数替代掉。

至此,Hello World已经写完了。访问应用根目录就能看到:

Hello 你好

的页面了。

待续。

下一篇文章:利用SAE搭建微信公众平台(二)

最近在准备毕设开题,要写论文,有各种参考文献。网上找了一下word中插入参考文献的方法,主要是尾注的形式。想起来之前在学校图书馆网站上下载过一个叫做NoteExpress的软件,可以方便的管理和插入参考文献,把具体使用方法写出来分享一下。

一、下载软件:http://www.reflib.org/download_chs.htm。这里面有各个大学、科研单位图书馆以及国图版的软件下载。可以免费获得正版授权。找自己对应的学校下载。

二、安装软件:过程不赘述。跟大多数软件安装过程一样,如果没有意外的话,Word插件也会同时装好。只是64位的Word需要单独下载和安装,下载连接中有安装说明,并且插件暂时只有英文版,我的就是,所以只能在用英文的插件来进行演示,好在非常简单。

三、管理文献:

3.1新建数据库:

1.文件->新建数据库

新建数据库1

新建数据库1

2.选择数据库路径。并选择复制文件到附件文件夹,这样可以更方便管理论文集。

新建数据库2

新建数据库2

3.2检索文献:

其实可以跳过这步,完全可以用手动输入文献的相关信息来代替。但是如果想偷懒的话,就完全可以利用NoteExpress的检索功能来帮你自动完成文献信息。

1.检索->在线检索->选择在线数据库。选择一个数据库,可以选选择你下载文献的数据库,保证一定有该文献。

检索1

检索1

或者:选择一个已经检索过的数据库:

检索2

检索2

2.检索。选择检索的方式,点击开始检索。

检索3

检索3

3.双击检索出来的文件,可以编辑信息。如果不全可以补全。

检索4

检索4

4.保存检索结果:选择保存勾选的题录->至文件夹:题录

检索5

检索5

3.3插入附件:可以使的题录与文件关联。选择要要关联的题录,附件标签,右键添加文件。

附件

附件

3.4选择插入样式:设置出入到word中的样式

1.工具->样式->样式管理器

样式1

样式1

2.选择合适的样式,比如国标样式,可以直接收缩GB

样式2

样式2

3.双击样式名,可以更改样式。比如点击布局。

样式3

样式3

四、在word中插入和删除参考文献:

4.1插入

1.在word工具栏中选择NoteExpress 工具栏,点击双窗口显示,这样比较好操作。

插入1

插入1

2.选择你要插入的文献,光标保持在腰插入的位置,点击插入引用。

插入2

插入2

3.这是插入后的参考文献列表,已经已国标形式展示。

插入3

插入3

4.2删除:和官方某些文档上不太一样,因为没有直接的删除引用按钮。但是可以用如下方式实现

1.选择编辑引用

删除1

删除1

2.选择要删除的引用,点击删除,确定。

删除2

删除2

3.删除之后的参考文献,对比插入的参考文献。

删除3

删除3

五、更多帮助:

1.NoteExpress 与endNote的比较。

2.NoteExpress使用教程,百度文库

在国内研究自然语言处理的人大概都知道ICTCLAS分词系统的大名。该系统是由张华平博士开发的基于层叠隐性马可夫链的分词系统,在中文分词领域具有领先的优势。今年一月张华平博士已经放出了最新的ICTCLAS2013分词系统,并更名为NLPIR,加入了新词发现,微博分词等功能。

在官方网站上,提供了C,Java,C#等语言的绑定。本文将介绍把最新的NLPIR分词系统与Python绑定的方法。

首先下载swig,swig可以帮助我们将C或者C++编写的DLL或者SO文件绑定到包括Python在内的多种语言。Windows下将安装包下载到一定目录下将该目录加入环境变量的path中即可使用swig(当然也可以输入完整的路径来使用swig)。可以打开命令行窗口,在里面输入swig,如果出现“Must specify an input file. Use -help for available options.”则表示一切顺利。

下面通过swig来讲NLPIR系统封装成Python的扩展。新建一个空白的目录,取名为nlpirpy_ext。首先要写的是swig的接口文件,可以参考swig文档的写法,下面就简单介绍一下:

[code]
%module NLPIR
%{
#define SWIG_FILE_WITH_INIT
#include "NLPIR.h"
%}
/*封装所有头文件中的api*/
#define POS_MAP_NUMBER 4
#define ICT_POS_MAP_FIRST 1
#define ICT_POS_MAP_SECOND 0
#define PKU_POS_MAP_SECOND 2
#define PKU_POS_MAP_FIRST 3
#define POS_SIZE 40
#define GBK_CODE 0
#define UTF8_CODE GBK_CODE+1
#define BIG5_CODE GBK_CODE+2
#define GBK_FANTI_CODE GBK_CODE+3
bool NLPIR_Init(const char * sDataPath=0,int encode=GBK_CODE);
bool NLPIR_Exit();
const char * NLPIR_ParagraphProcess(const char *sParagraph,int bPOStagged=1);
const result_t * NLPIR_ParagraphProcessA(const char *sParagraph,int *pResultCount,bool bUserDict=true);
int NLPIR_GetParagraphProcessAWordCount(const char *sParagraph);
void NLPIR_ParagraphProcessAW(int nCount,result_t * result);
double NLPIR_FileProcess(const char *sSourceFilename,const char *sResultFilename,int bPOStagged=1);
unsigned int NLPIR_ImportUserDict(const char *sFilename);
int NLPIR_AddUserWord(const char *sWord);//add by qp 2008.11.10
int NLPIR_SaveTheUsrDic();
int NLPIR_DelUsrWord(const char *sWord);
double NLPIR_GetUniProb(const char *sWord);
bool NLPIR_IsWord(const char *sWord);
const char * NLPIR_GetKeyWords(const char *sLine,int nMaxKeyLimit=50,bool bWeightOut=false);
const char * NLPIR_GetFileKeyWords(const char *sFilename,int nMaxKeyLimit=50,bool bWeightOut=false);
const char * NLPIR_GetNewWords(const char *sLine,int nMaxKeyLimit=50,bool bWeightOut=false);
const char * NLPIR_GetFileNewWords(const char *sFilename,int nMaxKeyLimit=50,bool bWeightOut=false);
unsigned long NLPIR_FingerPrint(const char *sLine);
int NLPIR_SetPOSmap(int nPOSmap);
bool NLPIR_NWI_Start();//New Word Indentification Start
int NLPIR_NWI_AddFile(const char *sFilename);
bool NLPIR_NWI_AddMem(const char *sText);
bool NLPIR_NWI_Complete();
const char * NLPIR_NWI_GetResult(bool bWeightOut=false);
unsigned int NLPIR_NWI_Result2UserDict();
[/code]

%module 后面跟的是模块名,也就是Python中import后跟名字。

%{
%}中的是放头文件的地方(所以说用swig做封装的话,至少得有头文件。)

其中“#define SWIG_FILE_WITH_INIT”是说封装成扩展的时候包含python的__init__.py的内容。”#include “NLPIR.h””大家懂的。

接下来那一堆#define也就是从头文件中复制出来的,这样在封装之后这些define的内容直接可以再模块中使用。

再接下来的函数声明就是要从C文件中导出的接口了。

下面再写一个setup.bat文件,将swig和扩展接口的工作统一起来:

[code]
swig -c++ -python NLPIR.i
python setup.py build_ext –inplace
echo "Build Complete!"
pause
[/code]

第一行就是swig的命令。-c++是说扩展的是C++编写的库, -python当然是说扩展为Python,NLPIR.i就是刚才写的接口文件

第二行我不懂……这是照别人写的,Python刚入门,对这些都还不太熟悉。包括接下来setup.py里面的内容也不太懂,都是依样画葫芦。大家可以参看以下这方面的教程。

[python]
”’
setup.py file for NLPIR
”’
from distutils.core import setup, Extension

NLPIR_module = Extension(‘_NLPIR’,sources=[‘NLPIR_wrap.cxx’], libraries = [‘NLPIR’])
setup(name = ‘NLPIR’,
version = ‘1.0’,
author = ‘SWIG Docs’,
description = ‘pyd for NLPIR’,
ext_modules = [NLPIR_module],
py_modules = [‘NLPIR’],
)
[/python]

要注意的两点是,第一是Extension的第一个参数前一定要有个”_”这是swig的规定,souces里面的cxx代表的就是c++文件。是第一步swig自动生成的文件。

接下来在目录里放置一个空白的__init__.py文件。为的是这个扩展可以直接放到python的目录中取。

最后,讲下载好的NLPIR文件夹中的NLPIR.dll,NLPIR.h,NLPIR.lib,Data文件夹放到和刚才同样的目录下。运行setup.bat

我就是在这里遇到了很大的问题的:我的是64位的win7系统,装的是64位的Python2.7.3。首先,在运行bat文件后,第一步能成功,在第二步的时候,首先Python会去查找VS2008的编译器,因为windows Python2.7.3本身就是VS2008编译的,但我恰恰装的是VS2012Express,所以查找失败。爆出:“Unable to find vcvarsall.bat”的错误,几经周折,发现如下解决方法:直接更改C:/Python32/Lib/distutils/msvc9compiler.py文件。。。然后失败了。。然后找到了改环境变量的方法:添加”VS90COMNTOOLS”指向VS2012的环境,例如C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools\。

到此时好像更进一步了,但是又遇到了新的问题:不是正确的win32文件。然后我发现问题出在Express版本的编译器不能编译64位文件……要么安装一个1.4G的windows的东西,要么换成32位版本的python……我表示败了,还是换32版本python吧,两个版本并存好了。

重新安装python32位版,更改环境变量指向新的Python,搞定。

上述工作完成后,将文件夹拷到python安装目录下的lib\site-packages目录下,就可以直接使用该扩展了。

下面是一个文中所提到的源代码的打包下载,一个是只含上述源代码的:nlpirpy_ext原始版.7z。另一个已经通过swig封装过了,目前只支持windows python2.7.3 32位版。其他环境需要自己封装或者测试,还带有一个小的Seg.py,是进一步封装的分词器类:NLPIRPY_EXT_built版.7z

本文就到这里了,欢迎指正:

参考文献: