分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019

技术文章列表

  • Android Studio使用POI读取及修改Word文档(.doc格式)

    一、前言如果你可爱的项目经理要求安卓端的你来操作word实现各种功能,不要犹豫,直接动之以情晓之以理,因为这本来就是java的poi,安卓虽然源自java,但对于java的很多东西是不支持的,已有的各种jar包也不方便更改,各种报错会搞的你脑阔疼。所以编辑word文档这种事让后台来做要比安卓来做简单的多,但如果实在避免不了,接着,给你代码。
    二、说明本篇不支持word2007版,只支持2003版,也就是只支持.doc格式,不支持.docx格式(想要支持.docx格式请参考“Android Studio使用POI读取及修改Word文档(.docx格式)”)。.doc格式的word文档是不支持图片插入的,因为.doc格式和.docx格式有很大的区别,用到的jar包和方法也不同,如果需要插入图片,请查看下篇对.docx格式word文档的处理。
    三、实现过程3.1 制作文书制作.doc格式的文档,然后导入项目:

    文章展示的文档内容如下:

    3.2 导包用到java poi-3.9中的两个包,完整压缩包点这里下载。然后复制用来编辑.doc文档的两个依赖包,导入libs目录。

    如果提示报错,是因为jar中有重复文件,请看build配置。

    加入外部存储读写权限,以后还需要自行申请,代码中有体现:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    3.3 实现代码读写过程直接上代码了,一看就懂:
    Button go;//生成文件的所在的地址private static final String newPath = "/storage/emulated/0/hwpfdocument.doc";@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go = (Button) findViewById(R.id.go); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readWord(); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0); }}private void readWord() { try { //从assets读取我们的Word模板 InputStream is = getAssets().open("hwpfdocument.doc"); //创建生成的文件路径 File newFile = new File(newPath); //需要修改的字段放入map中 Map<String, String> map = new HashMap<String, String>(); map.put("${TITLE}", "标题"); map.put("${TXT}", "表格第一行"); map.put("${CONTENT}", "内容"); writeDoc(is, newFile, map); } catch (IOException e) { e.printStackTrace(); }}private void writeDoc(InputStream is, File newFile, Map<String, String> map) { try { //使用poi的HWPFDocument方法 HWPFDocument hdt = new HWPFDocument(is); //Range读取word文本内容 Range range = hdt.getOverallRange(); //replaceText替换文本内容 for (Map.Entry<String, String> entry : map.entrySet()) { range.replaceText(entry.getKey(), entry.getValue()); } ByteArrayOutputStream ostream = new ByteArrayOutputStream(); FileOutputStream out = new FileOutputStream(newFile, true); hdt.write(ostream); //输出字节流 out.write(ostream.toByteArray()); out.close(); ostream.close(); Toast.makeText(this, "文书已生成", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); }}
    3.4 效果展示
    附录关于Apache POI - HWPF和XWPF的说明可以参考这里。
    关于HWPF的API可以参考这里。左上角选择org.apache.poi.hwpf,然后左下角选择hwpfdocument即可。
    本文转载自:https://blog.csdn.net/qq_21972583/article/details/82385940
    1 留言 2019-10-06 11:36:29 奖励15点积分
  • Android Studio使用POI读取及修改Word文档(.docx格式)

    一、说明上一篇文章(Android Studio使用POI读取及修改Word文档(.doc格式))使用poi对.doc格式的word文档进行了读取和更改,但很多情况下还需要在word文档中插入图片,这时就需要对.docx格式的word进行操作了。
    二、实现过程2.1 制作文书文书在源代码中可以直接看到,简单说明一下:文书有普通字段、表格、特定位置的图片,又在页眉页脚中加了普通字段和表格,基本满足对于word操作的所有情况。

    2.2 导包还是上篇中poi-3.9压缩资源包中的jar包,对.docx格式文档的操作用到XWPFDocument方法,使用到所有ooxml相关的jar包。

    2.3 build配置这次的build配置有点特殊,特别拿出来截图一下。就像上一篇说的一样,apache的很多配置在安卓是跑不通的,这次导包后,你会遇到方法过多,文件重复,基于jdk1.6以上版本的变异保存等一系列问题,可以按照下面的方法处理。当然不同的android studio版本可能也会有不同的处理方法,可以百度一下。

    权限还是储存权限,直接上代码吧,注解的也很详细。
    2.4 实现源码public class MainActivity extends AppCompatActivity { //创建生成的文件地址 private static final String newPath = "/storage/emulated/0/test.docx"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0); } Button go = (Button) findViewById(R.id.go); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { initData(); } }); } private void initData() { Map<String, Object> map = new HashMap<String, Object>(); map.put("$TITLE$", "标题");//$TITLE$只是个标识符,你也可以用${},[]等等 map.put("$TXT1$", "表格第一行"); map.put("$CONTENT1$", "第一行内容"); map.put("$TXT2$", "表格第二行"); map.put("$CONTENT2$", "第二行内容"); map.put("$CONTENT3$", "页脚中的内容"); map.put("$TXT4$", "页脚表格第一行"); map.put("$CONTENT4$", "页脚第一行内容"); map.put("$TXT5$", "页脚表格第二行"); map.put("$CONTENT5$", "页脚第二行内容"); try { //读取示例文书 InputStream is = getAssets().open("test.docx"); //自定义的XWPFDocument,解决官方版图片不显示问题 CustomXWPFDocument document = new CustomXWPFDocument(is); //读取段落(一般段落,页眉页脚没办法读取) List<XWPFParagraph> listParagraphs = document.getParagraphs(); processParagraphs(listParagraphs, map); //读取页脚 List<XWPFFooter> footerList = document.getFooterList(); processParagraph(footerList, map); //处理表格 Iterator<XWPFTable> it = document.getTablesIterator(); while (it.hasNext()) {//循环操作表格 XWPFTable table = it.next(); List<XWPFTableRow> rows = table.getRows(); for (XWPFTableRow row : rows) {//取得表格的行 List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) {//取得单元格 if ("$IMG$".equals(cell.getText())) { //直接插入图片会在文档的最底端,所以要插在固定位置,要把图片放在表格里 //所以使用判断单元格,并清除单元格放置图片的方式来实现图片定位 cell.removeParagraph(0); XWPFParagraph pargraph = cell.addParagraph(); document.addPictureData(getAssets().open("1.png"), XWPFDocument.PICTURE_TYPE_PNG); document.createPicture(document.getAllPictures().size() - 1, 120, 120, pargraph); } List<XWPFParagraph> paragraphListTable = cell.getParagraphs(); processParagraphs(paragraphListTable, map); } } } FileOutputStream fopts = new FileOutputStream(newPath); document.write(fopts); if (fopts != null) { fopts.close(); } } catch (Exception e) { e.printStackTrace(); } } //处理页脚中的段落,其实就是用方法读取了下页脚中的内容,然后也会当做一般段落处理 private void processParagraph(List<XWPFFooter> footerList, Map<String, Object> map) { if (footerList != null && footerList.size() > 0) { for (XWPFFooter footer : footerList) { //读取一般段落 List<XWPFParagraph> paragraphs = footer.getParagraphs(); processParagraphs(paragraphs, map); //处理表格 List<XWPFTable> tables = footer.getTables(); for (int i = 0; i < tables.size(); i++) { XWPFTable xwpfTable = tables.get(i); List<XWPFTableRow> rows = xwpfTable.getRows(); for (XWPFTableRow row : rows) {//取得表格的行 List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) {//取得单元格 List<XWPFParagraph> paragraphListTable = cell.getParagraphs(); processParagraphs(paragraphListTable, map); } } } } } } //处理段落 public static void processParagraphs(List<XWPFParagraph> paragraphList, Map<String, Object> param) { if (paragraphList != null && paragraphList.size() > 0) { for (XWPFParagraph paragraph : paragraphList) { List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { String text = run.getText(0); if (text != null) { boolean isSetText = false; for (Map.Entry<String, Object> entry : param.entrySet()) { String key = entry.getKey(); if (text.indexOf(key) != -1) { isSetText = true; Object value = entry.getValue(); if (value instanceof String) {//文本替换 text = text.replace(key, value.toString()); } } } if (isSetText) { run.setText(text, 0); } } } } } }}
    2.5 效果展示
    本文转载自:https://blog.csdn.net/qq_21972583/article/details/82740281
    1 留言 2019-10-08 13:02:23 奖励16点积分
  • 基于Skin++库实现的换肤功能

    背景之前自己经常使用MFC来开发一些界面程序,这些程序大都是自己练手用的。但,也会有极个别是帮别人开发,给别人使用。当你辛苦做出来的作品拿出去给别人用的时候,你总想让自己的作品给人留下深刻印象,无论是从功能,还是用程序界面上。
    对于,我们使用 VC6.0 或者 VS2008、VS2010等以上版本开发出来的界面程序,界面通常都是千篇一律,非常质朴的。所以,自己就像用最简单的方式去修改下界面,因为界面虽说重要,但也并不是说不可或缺的,所以自己不想花费太多的精力在界面上。
    后来,经过搜索查找,还真让我找到一个不错的、方便易用的界面修改方法,即使用 Skin++界面库 来实现。Skin++ 属于第二代的外挂式的界面库,提供了Skin Builder 工具将所有控件的资源全部设计成为一个独立的文件,便于在应用程序外部独立地进行增删改操作,采用Hook与子类化技术来实现应用程序的自动换肤。
    现在,本文就基于 Skin++界面库 实现给 VC 或 VS 开发的界面程序更换皮肤。把实现的过程整理成文档,分享给大家。
    实现过程程序要使用到的 Skin++库 文件包括:SkinPPWTL.h、SkinPPWTL.lib、skinppwtl.dll 以及 48个 .ssk 的皮肤文件。
    首先,我们需要把 Skin++ 库文件加载到程序里,做法如下:

    把 SkinPPWTL.h 头文件以及 SkinPPWTL.lib 库文件拷贝到我们的项目工程目录下面,然后在程序声明头文件并加载库文件:
    #include <afxcmn.h>#include <Windows.h>#include "skin\\SkinPPWTL.h"#pragma comment(lib, "skin\\SkinPPWTL.lib")
    然后,我们编译下程序,看看有没有错误提示。若出现类似这样的错误提示: “… _CRT_SECURE_NO_WARNINGS …”,则在 项目属性 —> C/C++ —> 预处理器 —> 预处理器定义 中添加 “__CRT_SECURE_NO_WARNINGS” 这个预定义即可。

    经过上面两步操作,就可以正确地把 Skin++ 库文件加载到程序中了。
    接下来,我们就直接调用 skinppLoadSkin 函数加载 .ssk 格式的皮肤库就可以了。但是要注意调用 skinppLoadSkin 函数的地方,一定要在界面实现出来之前的初始化操作里就开始调用,不能再界面显示出来后调用,否则会出问题。对于 MFC 程序和 Windows应用程序,它们调用 skinppLoadSkin 函数加载界面库文件的地方可以是:

    对于 MFC 程序,可以是在 CxxxAPP::InitInstance() 初始化函数的开头调用,也可以是在 CxxxDlg::OnInitDialog() 主窗口类初始化函数里调用。
    对于 Windows 应用程序,可以是在 WinMain 函数的开头调用,也可以是在窗口消息处理过程函数中的 WM_INITDIALOG 消息中加载。

    调用 skinppLoadSkin 函数加载界面库文件的代码如下所示:
    // 加载皮肤::skinppLoadSkin("skins\\XP-Home.ssk");
    要更换皮肤,只需要更改上面加载的 .ssk 库文件就好,本文有 48 个 .ssk 文件,所以,可以实现 48 种换肤。
    最后,我们编译链接生成可执行文件,在运行程序之前,只需要按照上面库文件 .ssk 的路径放置库文件,同时还需要把 skinppwtl.dll 动态链接库放在和可执行程序同一目录下,就可以正常运行程序了。
    程序测试按照上面的方法,我们分别创建了一个 MFC 程序和 Windows应用程序 来进行测试,程序成功更换皮肤。


    总结对于 Skin++界面库使用比较简单,主要在窗口初始化的时候,就对界面库初始化,并加载界面库就好,剩下的,不需要我们去理会,我们只需要正常开发我们的程序功能就好。
    3 留言 2018-11-29 09:23:21 奖励10点积分
  • 基于python构建搜索引擎系列——(四)检索模型 精华

    构建好倒排索引之后,就可以开始检索了。
    检索模型有很多,比如向量空间模型、概率模型、语言模型等。其中最有名的、检索效果最好的是基于概率的BM25模型。
    给定一个查询Q和一篇文档d,d对Q的BM25得分公式为:

    公式中变量含义如下:

    qtf:查询中的词频
    tf:文档中的词频
    ld:文档长度
    avg_l:平均文档长度
    N:文档数量
    df:文档频率
    b,k1,k3:可调参数

    这个公式看起来很复杂,我们把它分解一下,其实很容易理解。第一个公式是外部公式,一个查询Q可能包含多个词项,比如“苹果手机”就包含“苹果”和“手机”两个词项,我们需要分别计算“苹果”和“手机”对某个文档d的贡献分数w(t,d),然后将他们加起来就是整个文档d相对于查询Q的得分。
    第二个公式就是计算某个词项t在文档d中的得分,它包括三个部分。第一个部分是词项t在查询Q中的得分,比如查询“中国人说中国话”中“中国”出现了两次,此时qtf=2,说明这个查询希望找到的文档和“中国”更相关,“中国”的权重应该更大,但是通常情况下,查询Q都很短,而且不太可能包含相同的词项,所以这个因子是一个常数,我们在实现的时候可以忽略。
    第二部分类似于TFIDF模型中的TF项。也就是说某个词项t在文档d中出现次数越多,则t越重要,但是文档长度越长,tf也倾向于变大,所以使用文档长度除以平均长度ld/avg_l起到某种归一化的效果,k1和b是可调参数。
    第三部分类似于TFIDF模型中的IDF项。也就是说虽然“的”、“地”、“得”等停用词在某文档d中出现的次数很多,但是他们在很多文档中都出现过,所以这些词对d的贡献分并不高,接近于0;反而那些很稀有的词如”糖尿病“能够很好的区分不同文档,这些词对文档的贡献分应该较高。
    所以根据BM25公式,我们可以很快计算出不同文档t对查询Q的得分情况,然后按得分高低排序给出结果。
    下面是给定一个查询句子sentence,根据BM25公式给出文档排名的函数:
    def result_by_BM25(self, sentence): seg_list = jieba.lcut(sentence, cut_all=False) n, cleaned_dict = self.clean_list(seg_list) BM25_scores = {} for term in cleaned_dict.keys(): r = self.fetch_from_db(term) if r is None: continue df = r[1] w = math.log2((self.N - df + 0.5) / (df + 0.5)) docs = r[2].split('\n') for doc in docs: docid, date_time, tf, ld = doc.split('\t') docid = int(docid) tf = int(tf) ld = int(ld) s = (self.K1 * tf * w) / (tf + self.K1 * (1 - self.B + self.B * ld / self.AVG_L)) if docid in BM25_scores: BM25_scores[docid] = BM25_scores[docid] + s else: BM25_scores[docid] = s BM25_scores = sorted(BM25_scores.items(), key = operator.itemgetter(1)) BM25_scores.reverse() if len(BM25_scores) == 0: return 0, [] else: return 1, BM25_scores
    首先将句子分词得到所有查询词项,然后从数据库中取出词项对应的倒排记录表,对记录表中的所有文档,计算其BM25得分,最后按得分高低排序作为查询结果。
    类似的,我们还可以对所有文档按时间先后顺序排序,越新鲜的新闻排名越高;还可以按新闻的热度排序,越热门的新闻排名越高。
    关于热度公式,我们认为一方面要兼顾相关度,另一方面也要考虑时间因素,所以是BM25打分和时间打分的一个综合。
    比较有名的热度公式有两个,一个是Hacker News的,另一个是Reddit的,他们的公式分别为:


    可以看出,他们都是将新闻/评论的一个原始得分和时间组合起来,只是一个用除法,一个用加法。所以我们也依葫芦画瓢,”自创“了一个简单的热度公式:

    用BM25得分加上新闻时间和当前时间的差值的倒数,k1k1和k2k2也是可调参数。
    按时间排序和按热度排序的函数和按BM25打分排序的函数类似,这里就不贴出来了,详细情况可以看我的项目News_IR_Demo。
    至此,搜索引擎的搜索功能已经实现了,你可以试着修改./web/search_engine.py的第167行的关键词,看看搜索结果是否和你预想的排序是一样的。不过由于我们的数据量只有1000个新闻,并不能涵盖所有关键词,更多的测试可以留给大家线下完成。
    本文转载自:http://bitjoy.net/2016/01/07/introduction-to-building-a-search-engine-4
    2 留言 2019-06-01 15:29:45 奖励14点积分
  • windows下静态使用QxOrm框架并使用泛型编程 (二)

    这篇开始讲实际编程并且抽象化,让代码书写更少。
    为了模块划分我在Demo文件夹下新增了一个SQLModule文件夹,在此文件夹下又新增QxHandler,QxMapped,QxObject三个文件夹,QxObject是用来存储数据对象类的一个文件夹,QxMapped是存储键值映射的文件夹,QxHandler是操作数据库的实际句柄类文件夹。
    QxObject类
    User.h
    #ifndef USER_H#define USER_H#include "common.h"#include <QxOrm.h>#include <QJsonObject>class User{ QX_REGISTER_FRIEND_CLASS(User) //用于将类注册到QxOrm的宏定义public: User(); long getId(); QString getName(); int getAge();public: QJsonObject toJsonObject(); bool analyzeJson(QByteArray &json); void analyzeJson(QJsonObject &json);private: long m_lId QString m_sName; int m_nAge;};//QX_REGISTER_PRIMARY_KEY(User, int) //主键不是整数类型的时候使用QX_REGISTER_HPP_IMPORT_DLL(User, qx::trait::no_base_class_defined, DATABASE_VERSION) //用于将类注册到QxOrm的宏定义 第一个参数为类名 第二个为默认值 第三个为数据库版本#endif // USER_HUser.cpp
    #include "User.h"QX_REGISTER_CPP_IMPORT_DLL(User) //用于将类注册到QxOrm的宏定义namespace qx //用于将类注册到QxOrm的方法 { template <> void register_class(QxClass<User> & t) { qx::IxDataMember * pData = NULL; Q_UNUSED(pata); qx::IxSqlRelation * pRelation = NULL; Q_UNUSED(pRelation); qx::IxFunction * pFct = NULL; Q_UNUSED(pFct) qx::IxValidator * pValidator = NULL; Q_UNUSED(pValidator); // Register pData =t.id(& User::m_lId, "id",DATABASE_VERSION); pData =t.data(& User::m_nAge, "name",DATABASE_VERSION); pData =t.data(& User::m_nAge, "age",DATABASE_VERSION); qx::QxValidatorX<User> * pAllValidator = t.getAllValidator(); Q_UNUSED(pAllValidator); }}User::User() :m_lId(0) ,m_sName("") ,m_nAge(0){}long User::getId(){ return m_lId;}QString User::getName(){ return m_sName;}int User::getAge(){ return m_nAge}JsonObject User::toJsonObject(){ QJsonObject subObject; subObject.insert("Name",m_sName); subObject.insert("Age",m_nAge); return subObject;}bool User::analyzeJson(QByteArray &json){ bool success=false; QJsonParseError error; QJsonDocument document=QJsonDocument::fromJson(json,&error); if (!document.isNull() && (error.error == QJsonParseError::NoError)) { QJsonObject rootObject =documnt.object(); this->analyzeJson(rootObject); success=true; } else { success=false; qDebug()<<error.error; qDebug("LockoutFun analyze falied"); } return success;}void User::analyzeJson(QJsonObject &json){ QJsonObject rootObject = json.value("User").toObject(); m_sName=rootObject.value(QString("Name")).toString(); m_nAge=rootObject.value(QString("Age")).toInt();}QxMapped
    IMapped 映射抽象类
    #ifndef IMAPPED_H#define IMAPPED_H/*** @author tianmin* @brief The IMapped class 抽象出来的映射类* @date 2019-07-22*/#include <QStringList>#include <QSting>#include <QMapclass IMapped{public //字符map初始化 virtual void initMapped() = 0; //获取QString列表 virtual QStringList getListMapped(int en) =0; //获取QString 字段 virtual QString getMapped(int en) = 0; virtual ~IMapped(){}};#endif // IMAPPED_HUserMapped 际使用的类
    UserMapped .h
    #ifndef USERMAPPED_H#define USERMAPPED_H#include "IMapped.h"namespace USER {enum USER_MAPPED{ EN_ID = 0x00000001, EN_NAME = 0x00000002, EN_AGE = 0x00000004, EN_ALL = 0xFFFFFFFF};}class UserMapped:public IMapped{public: UserMapped(); virtual ~UserMapped();public: virtual void initMapped() virtual QStringList getListMapped(int en); virtual QString getMapped(int en);private: QMap<int,QString> m_map;};#endif // USERMAPPED_HUserMapped.cpp
    #include "UserMapped.h"UserMapped::UserMapped(){}UserMapped::~UserMapped(){}void UserMapped::initMapped(){ m_map.clear(); m_map.insert(USER::EN_ID, "id"); m_map.insert(USER::EN_NAME ,"name"); m_map.insert(USER::EN_AGE ,"age");}QStringList UserMapped::getListMapped(int en){ QStringList temp; QString str; QMap<int,QString>::iterator i; or (i = m_map.begin(); i != m_map.end(); ++i) { if(en&(i.key())) { str=i.value(); temp.append(str); str.clear(); } } return temp;}QString UserMapped::getMapped(int en){ QString str QMap<int,QString>::iterator i; for (i = m_map.begin(); i != m_map.end(); ++i) { if(en==i.key()) { str =i.value(); return str; } } return QString();}QxHandler
    IHandler 抽象化的类模板
    这里只抽象了部分方法 还有save 和事务以及关系的方法未抽象完成
    #ifndef IHANDLER_H#define IHANDLER_H#include <QxOrm.h>#include <QMutexLocker>#include <QMutex>#include <QtSql/QSqlDatabase>#include <QtSql/QSqlError>/*** @brief The ISqlInterface class 数据库操作的抽象方法类 并且模板化减少代码的繁杂*/class ISqlInterface{public: ISqlInterface(){} virtual ~ISqlInterface(){} //数据库连接初始化protected: virtual void initSqlconnect()=0; //建表 virtual bool createTable()=0; //断开连接 virtual void disconnect()=0;};template<class T,class T2,class T3>class IHandler{public: IHandler(){} virtual ~IHandler(){} Virtual bool createTable(QSqlDatabase &m_SqlDatabase) { QSqlError error= qx::dao::create_table<T3>(&m_SqlDatabase); return !error.isValid(); } virtual bool insert(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; if(qx::dao::exist(t,&m_SqlDatabase).getValue()!=false) return false; QSqlError error= qx::dao::insert(t,&m_SqlDatabase); return !error.isValid(); } virtual bool deleteObject(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,bool isDestroy) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; QSqlError error; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; if(isDestroy==false) { error= qx::dao::delete_by_id(t,&m_SqlDatabase); } else { error= qx::dao::destroy_by_id(t,&m_SqlDatabase); } return !error.isValid(); } virtual bool update(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,QStringList &list) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabas.isOpen()) return false; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; QSqlError error= qx::dao::update(t,&m_SqlDatabase,list); return !error.isValid(); } virtual bool select(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,QStringList &list) QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; QSqlError error; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; error= qx::dao::fetch_by_id(t,&m_SqlDatabase,list); return !error.isValid(); } virtual bool selectByQuery(T2 &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,qx::QxSqlQuery &query,QStringList &list) { QMutexLocker locker(&m_Mutx); if(!m_SqlDatabase.isOpen()) return false; QSqlError error=qx::dao::fetch_by_query(query,t,&m_SqlDatabase,list); return !error.isValid(); }};#endif // IHANDLER_HUserHandler 实际操作句柄类
    UserHandler.h
    #ifndef USERHANDLER_H#define USERHANDLER_H#include <QxOrm.h>#include <QString>#include <QMutexLocker>#include <QMutex>#include <QtSql/QSqlDatabase>#include <QtSql/QSqlError>#include "IHandler.h"#include "SQLModule/QxMapped/UserMapped.h"#include "SQLModule/QxObject/User.h"namespace USER{const QString DATABASE_TYPE="QSQLITE";const QString CONNECT_NAME="USER_CONNECTED";const QString DATABASENAME="C:/Users/we/Desktop/workTools/demo/qxorm.db";const QString HOSTNAME="localhost";const QString USERNAME="root";const QString PASSWORD="";}using namespace USER;class User; //OBJECT 类typedef QSharedPointer<User> Shared_User; //User类智能指针typedef QList<Shared_User> List_User; //User类数组typedef qx::QxCollection<int,Shared_User> Collection_User; //User容器class UserHandler:public IHandler<Shared_User,Collection_User,User> ,public ISqlInterface{public: UserHandler(); virtual ~UserHandler(); /** * @brief insert 插入数据至数据库 * @param t 插入的单条数据 不需要指定ID值 自增 * @return 0失败 1成功 */ bool insert(Shared_User &t); /** * @brief deleteObject 从数据库中删除指定数据 * @param t 删除的单条数据 需要指定ID值 * @param isDestroy 是否软删除 * @return 0失败 1成功 */ bool deleteObject(Shared_User &t,bool isDestroy=false); /** * @brief update 根据ID值更新数据 * @param t 数据 * @param en 更新字段的映射值 * @return 0失败 1成功 */ bool update(Shared_User &t,int en=EN_ALL); /** * @brief select 根据ID值查询数据 * @param t 数据 * @param en 映射值 * @return 0失败 1成功 */ bool select(Shared_User &t,int en=EN_ALL); /** * @brief selectByQuery 根据搜寻条件查找 * @param t 数据集合 * @param query 搜寻语句 * @param en 数据库列映射值 * @return 0失败 1成功 */ bool selectByQuery(Collection_User &t,qx::QxSqlQuery &query,int en=EN_ALL);protected: virtual void initSqlconnect(); virtual bool createTable(); virtual void disconnect();private: UserMapped m_Mapped; QMutex m_Mutex; QSqlDatabase m_SqlDatabase;};#endif // USERHANDLER_HUserHandler.cpp
    #include "UserHandler.h"UserHandler::UserHandler(){ initSqlconnect(); createTable();}UserHandler::~UserHandler(){ disconnect();}void UserHandler::initSqlconnect(){ QMutexLocker locker(&m_Mutex); if(QSqlDatabase::contains(CONNECT_NAME)) m_SqlDatabase = QSqlDatabase::database(CONNECT_NAME); else m_SqlDatabase= QSqlDatabase::addDatabase(DATABASE_TYPE,CONNECT_NAME); m_SqlDatabase.setDatabaseName(DATABASENAME); m_SqlDatabase.setHostName(HOSTNAME); m_SqlDatabase.setUserName(USERNAME); m_SqlDatabase.setPassword(PASSWORD); m_SqlDatabase.open();}bool UserHandler::createTable(){ return IHandler<Shared_User,Collection_User,User>::createTable(m_SqlDatabase);}void UserHandler::disconnect(){ QMutexLocker locker(&m_Mutex); if(m_SqlDatabase.isOpen()) m_SqlDatabase.close(); QSqlDatabase::removeDatabase(CONNECT_NAME);}bool UserHandler::insert(Shared_User &t){ return IHandler<Shared_User,Collection_User,User>::insert(t,m_Mutex,m_SqlDatabase);}bool UserHandler::deleteObject(Shared_User &t,bool isDestroy){ return IHandler<Shared_User,Collection_User,User>::deleteObject(t,m_Mutex,m_SqlDatabase,isDestroy);}bool UserHandler::update(Shared_User &t, int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::update(t,m_Mutex,m_SqlDatabase,list);}bool UserHandler::select(Shared_User &t, int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::select(t,m_Mutex,m_SqlDatabase,list);}bool UserHandler::selectByQuery(Collection_User &t,qx::QxSqlQuery &query,int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::selectByQuery(t,m_Mutex,m_SqlDatabase,query,list);}
    1 留言 2019-07-23 14:38:57 奖励6点积分
  • PC微信逆向分析のWeTool内部探秘 精华

    作者:zmrbak(赵庆明老师)
    前言先不说微信在社交领域的霸主地位,我们仅从腾讯公司所透露的在研发微信过程中踩过的无数的坑,以及公开的与微信相关的填坑的源码中,我们可以感受到,单从技术上讲,微信是一款非常伟大的产品。然而,伟大的产品,往往会被痴迷于技术的人送进实验室,运用各种可能的工具将其大卸八块,以参悟其“伟大”之所在!。
    WeTool,一款免费的微信社群管理工具,正是一群痴迷于技术的人对于微信这个伟大的产品的研究而得到的成果。在微商界,这个软件真可谓是鼎鼎大名、如雷贯耳。如果你还不知晓这个软件,那么你肯定不是微商界的人。如果你想对你的微信群进行管理,而又不想花钱,也许这个软件就是你最佳的选择。当然,免费软件的套路都是一样的,WeTool“有意地”不满足你的一些特殊需求,如果真的很想要的话,当然是要付费的,那就购买“企业版”吧。
    但是,对于一个对技术有强烈兴趣的人来说,研究WeTool与研究PC微信一样有趣,在这里,我把它们两个一起送进实验室,一窥其中的奥秘!
    微信中的WeTool由于腾讯干预,目前WeTool免费版本已不再公开提供下载。但之前的旧版本仍然可以自动升级到最新版。如果你想获得WeTool这个软件,我想,你应该知道该怎么做了吧。如果你还是不知道,很抱歉,这篇文章对你来说太深奥了。那么我对你的建议是:关掉这个网页吧。
    WeTool在启动的时候,会检查当前计算机上是否安装了版本匹配的PC微信。倘若找不到,或者版本不匹配,WeTool会引导你到它的官网去下载一个版本匹配的PC微信(可能比较旧,但能用)。下载完毕后,还需要你手动去安装一下。
    在WeTool启动的时候,还会检查微信的登录状态,如果微信还未完成登录,WeTool会等待微信登录之后,再开启自己的管理界面。
    这里的问题是:WeTool是如何得知微信是否已经登录了呢?
    在这里,我们使用PCHunter来检查一下微信(WeChat.exe)的进程模块。我们可以看到,在微信的进程中加载了一个特殊的DLL文件(WeHelp.dll),而它的父目录是一个特殊的字符串:“2.6.8.65”,恰好与我们当前运行的微信版本一致。再上一层的目录,“WeToolCore”,很明显,这里的文件是WeTool的一部分。

    恰恰是这个DLL文件帮助WeTool完成了与微信之间的各种互动。也就是说,WeTool通过WeHelp.dll这个文件,可以感知到微信的各种活动,当然也包括微信是否已经登录等等…
    窥探WeTool如果在不经意之间关闭了WeTool,你会发现,你的微信也被关闭了。这又是为什么呢?
    如果你曾经用OD调试过软件,你会发现当你的OD被关闭的时候,被OD所调试的那个软件也被关闭掉了。因此,我们猜想,WeTool对于微信来说,应该使用的是类似于OD之于其他软件相同的原理,那就是“调试”。
    在WeTool管理你的微信的时候,你也会发现,这时候微信无法被OD所附加。其实,还是“调试”。当一个软件已经处于某个调试器的“调试”之下,为了防止出错,调试器会拒绝对这个已处于被调试中的软件的再次调试。这进一步印证了WeTool对于微信的“调试”的事实。
    然而就是这么一个“小小的”设置,就击碎不少“小白”想调试WeTool美梦。
    既然我们找到了WeTool对于微信的关键,那就是文件“WeHelp.dll”。那么,我们就把这个文件请入我们的实验室,让我们把它一点一点地拆开,细细探寻其中的一点一滴的奥秘。
    拆解WeTool在动手拆解之前,我们还是先了解一下WeTool到底向我们的计算机上安装了些什么东东。顺着桌面上的“WeTool 免费版”,我们找到了WeTool安装的目录,安装目录之下22个文件夹和84个文件。当然,让我们比较感兴趣的就是“WeChatVersion”这个文件夹,因为它的名字与微信(WeChat)太让人能联想到一起了。

    双击“WeChatVersion”,我们看到如下结果。恰好是以微信曾经的一个个版本号命名的文件夹。我们猜想,这个文件夹一定与这个版本的微信之间存在中某种联系。目前,我们可以得到最新的微信版本是2.6.8.68(此版本为更新版;从腾讯官网可下载到的版本仅为2.6.8.65),而这里恰好有一个以该版本号命名的文件夹“2.6.8.65”。

    我们双击打开“2.6.8.65”这个文件夹。文章前面所提到的“WeHelp.dll”文件赫然在目。点开其他类似微信版本号的文件夹,同样,每个文件夹中都有这两个文件。唯一的区别就是文件的大小不一样。
    由于我们使用的微信版本是2.6.8.65,那么我们就针对2.6.8.65文件夹下的这个“WeHelp.dll”进行研究。通过二进制对比,我们发现该文件夹下的“WeHelp.dll”文件与微信中加载的“WeHelp.dll” 文件为同一个文件。

    由此,我们得出结论:WeTool为不同版本的微信分别提供了不同的WeHelp.dll文件,在WeTool启动的时候,把WeChatVersion中对应与当前版本微信号的文件夹复制到当前Windows登录用户的应用程序数据文件夹中,然后再将里面的“WeHelp.dll”加载到微信进程的内存中。
    WeHelp解析WeTool为“WeHelp.dll”设置了一道阻止“动态调试”的障碍,这足以让所有的动态调试器,在没有特殊处理前,对它根本无法下手。
    如果能绕道而行,那何必强攻呢?于是我们请出静态分析的利器——IDA PRO 32。注意,这里务必使用32位版本的,因为只有在32位版本中,才可以把汇编代码转换成C语言的伪代码。相比于汇编代码来说,C代码就直观的多了。
    打开IDA,点击按钮“GO”,然后把WeHelp.dll拖入其中,接下来就是十几秒的解析,解析完毕后,界面如下:

    从IDA解析的结果中,让我们很惊奇的是,在“WeHelp.dll”中居然未发现什么加壳啊、加密啊、混淆啊等等这些对于程序版权保护的技术。也许是WeTool太自信了吧!毕竟WeTool是事实上的业界老大,其地位无人可以撼动。
    对于和微信之间交互的这部分功能来说,其实对于一个刚入门的、比较勤奋的逆向新手,只需经过半年到一年时间的练手,这部分功能也是可以完成。对于WeTool来说,其真正的核心价值不在这里,而在于其“正向”的管理逻辑,以及自己后台的Web服务器。在它的管理界面,各种功能实现里逻辑错综复杂,如果你想逆向的话,还不如重写算了,况且它都已经免费给你用了,还有必要逆向吗!!当然,WeTool后台的服务器,你根本就碰不到。
    从IDA解析的结果中,可以看到WeHelp中各个函数、方法,毫无遮拦地完全展示在眼前。而在右侧的窗口中,按下F5,瞬间汇编代码变成了C语言的伪代码。

    对于一个稍稍有一些Window API编程经验的人来说,这些全部都是似曾相识的C代码,只需简单地猜一猜,就能看明白写的是啥。如果还是不懂的话,那就打开Visual Studio,对照着看吧。这里是DllMain,也就是DLL的入口函数。我们还是来创建一个C++的动态链接库(dll)的项目,来对照着看吧:

    fdwReason=1,恰好,DLL_PROCESS_ATTACH=1。一旦DLL被加载,则马上执行DllMain这个函数中的DLL_PROCESS_ATTACH分支。也就是说,当“WeHelp.dll”这个文件被微信加载到进程之后,马上执行一下DllMain函数,DLL_PROCESS_ATTACH分支里面的这两个函数就会马上执行。

    鼠标双击第一个函数(sub_10003040),到里面去看看这个函数里面有啥,如下图,它的返回值来自于一个Windows Api(桃红色字体)——“RegisterWindowMessageW”,查看MSDN后,发现,原来是注册Windows消息。
    这不是我们最想要的,按ESC键,返回。
    鼠标双击下一个函数(sub_100031B0),页面变成这个啦。很明显,在注册一个窗口类。对于一个窗口来说,最重要的就是它的回调函数,因为要在回调函数中,完成对窗口的所有事件处理。这里,lpfnWndProc= sub_10003630,就很明显了,这就是回调函数。

    双击sub_10003630这个函数,窗口切换为如下内容。除了第一条语句的if之外,剩下的if…else if…else if是那么的引人注目。每一个比较判断之后,都调用了一个函数。而判断的依据是传入的参数“lParam”要与一个dword的值比较。
    我们猜测,这些函数大概是WeHelp和微信之间交互相关的函数吧。当然,这只是猜测,我们还要进一步验证才行。

    sub_10003630这个函数,是窗口的回调函数,我们要重点关注。那么,我们先给它改个名字吧。在函数名上点右键,选中“Rename global item”,我们取个名字叫“Fn_WndProc”吧。于是页面就变成了这样:

    虽然在IDA中,“WeHelp.dll”中的函数(方法)全部显示出来了,但是也有40多个呢,我们找个简单一点的来试试。CWeHelp::Logout(void),这个函数没有参数,那么我们就选这个吧。在左侧函数窗口中双击CWeHelp::Logout(void),右侧窗口换成了这个函数的C语言伪代码(如果你显示的还是汇编,请点击汇编代码后按F5)。

    在前面,我们看到,在回调函数中,lParam要与一个dword值进行比较。在这个函数中,我们发现,这里为lParam赋了一个dword类型的值。为了方便记忆,我们把这个dowrd值改个名字吧,因为是Logout函数中用到的数字,那么就叫做“D_Logout”吧。

    接下来,我们要看看”还有谁”在用这个数值。在我们修改后的“D_Logout”上点右键,选择“Jump to xref…”。原来这个数值只有两个地方使用,一个就是当前的“Logout”函数,而另一个却是在”Fn_WndProc”中,那不就是前面的那个回调函数嘛!选中“Fn_WndProc”这一行,点击OK!

    又一次看到了熟悉的if…else if…,还有和“D_Logout”进行比较的分支,而这个分支里面只调用了一个函数sub_10005940,而且不带参数。

    双击函数“sub_10005940”后,发现这个函数很简单。核心语句只有两条,首先调用了sub_100030F0函数,然后得到一个返回值。接下来,为这个返回值加上一个数值“0x3F2BF0”,再转换成一个函数指针,再给一个0作为参数,再调用这个函数指针。最后返回结果。

    我们这里要关注,来自于“sub_100030F0”函数的返回值result到底是什么?
    同样,双击这个函数(sub_100030F0),进去看看呗!原来,调用了一个Windows API函数(GetModuleHandleW),查看MSDN后,发现原来这个函数的功能就是取微信的基址(WeChatWin.dll)。

    那就简单多了!是不是说,如果我们在微信中执行“((int (__stdcall *)(_DWORD))(weChatWinBaseAddress + 0x3F2BF0))(0);”这么一句代码,就可以实现Logout功能呢?
    当然,这只是猜测,我们还需要进一步验证。
    猜测验证打开原来创建的C++的动态链接库项目,把 case分支DLL_PROCESS_ATTACH换成如下内容:
    case DLL_PROCESS_ATTACH: { DWORD weChatWinBaseAddress = (int)GetModuleHandleW(L"WeChatWin.dll"); ((int(__stdcall*)(DWORD))(weChatWinBaseAddress + 0x3F2BF0))(0); }
    注意:把从IDA中拷贝过来的代码中 “_DWORD”中的下划线去掉,就可以编译通过了。

    启动微信,登录微信(这时候,手机微信中会显示“Windows微信已登录”)。
    使用OD附加微信(确保WeTool已经退出,否则OD附加不成功)。
    在OD汇编代码窗口点右键,选择”StrongOD\InjectDll\Remote Thread”,选中刚才Visual Studio中编译成功的那个dll文件。

    一秒钟!OK,神奇的事情发生了:微信提示,你已退出微信!
    同时,手机微信上原来显示的 “Windows微信已登录”,也消失了。
    从这里我们可以确定,微信“真的”是退出了,而不是崩掉了。

    总结其实逆向研究,并不只是靠苦力,更重要的是强烈的好奇心和发散的思维。也许一个瞬间,换一下思维模式,瞬间一切都开朗了。一个人的力量是有限的,融入一个圈子,去借鉴别人的成功经验,同时贡献自己成功经验,你会发现,逆向研究其实是一件非常有趣的事情。当然,我研究的后编写源码都是免费公开的,你可以到GitHub(https://github.com/zmrbak/PcWeChatHooK)上下载,也欢迎和我们一起学习和研究。
    后记2019年3月17日前,本人对WeTool进行过一些探索,始终没有取得进展。时隔两个月之后,突然有所发现,于是在2019年5月29日将我的探索成果录制成一个个视频,分享给和我一起研究和探索微信的好朋友。结果,在他们中激起了强烈的反响,有不少的朋友的研究进度有了一个飞跃性的发展,还有几个朋友短短几日之间,就远远超越了我的研究进度。看到这样的场景,让我的内心充满欢喜。当然,还有不少朋友的评价,更是让我开心,现摘录如下:











    源码分享:https://github.com/zmrbak/PcWeChatHooK
    2 留言 2019-09-14 15:14:33 奖励35点积分
  • 【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关) 精华

    一、涉及知识点
    场景切换按钮事件监听节点事件监听节点数组循环中闭包的应用动态更换sprite图片定时器预制资源
    二、步骤2.1 准备工作首先,我们要新建一个空白工程,并在资源管理器中新建几个文件夹

    在这些文件夹中,我们用来存放不同的资源,其中

    Scene用来存放场景,我们可以把场景看作一个关卡,当关卡切换时,场景就切换了
    Script用来存放脚本文件
    Texture用来存放显示的资源,例如音频,图片
    Prefab用来存放预制资源,接下来我会详细的介绍

    接下来,我们在Scene文件夹中新建一个场景(右击文件夹->新建->Scene),命名为Menu,接着导入背景图片(直接拖拽即可)。最后调整图片大小使图片铺满背景,效果如图。

    2.2 按钮监听与场景切换接下来我们来学习此次实战的第一个难点,按钮监听与场景切换。
    首先,创建一个Button节点,并删除label,放在人机博弈的按钮上,并在属性上调成透明样式。

    接下来,新建一个Game场景,并添加一个棋盘节点,并把锚点设为(0,0)。

    这里,讲一下锚点的作用。
    anchor point 究竟是怎么回事? 之所以造成不容易理解的是因为我们平时看待一个图片是以图片的中心点这一个维度来决定图片的位置的。而在cocos2d中决定一个图片的位置是由两个维度一个是 position 另外一个是anchor point。只要我们搞清楚他们的关系,自然就迎刃而解。默认情况下,anchor point在图片的中心位置(0.5, 0.5),取值在0到1之间的好处就是,锚点不会和具体物体的大小耦合,也即不用关注物件大小,而应取其对应比率,如果把锚点改成(0,0),则进行放置位置时,以图片左下角作为起始点。也就是说,把position设置成(x,y)时,画到屏幕上需要知道:到底图片上的哪个点放在屏幕的(x,y)上,而anchor point就是这个放置的点,anchor point是可以超过图片边界的,比如下例中的(-1,-1),表示从超出图片左下角一个宽和一个高的地方放置到屏幕的(0,0)位置(向右上偏移10个点才开始到图片的左下角,可以认为向右上偏移了10个点的空白区域)
    他们的关系是这样的(假设actualPosition.x,actualPosition.y是真实图片位置的中点):actualPosition.x = position.x + width*(0.5 - anchor_point.x);acturalPosition.y = position.y + height*(0.5 - anchor_point.y)actualPosition 是sprite实际上在屏幕显示的位置, poistion是 程序设置的, achor_point也是程序设置的。
    然后,我们需要新建一个脚本,Menu.js,并添加开始游戏方法。
    cc.Class({ extends: cc.Component, startGame:function(){ cc.director.loadScene('Game');//这里便是运用导演类进行场景切换的代码 }});
    这里提示以下,编辑脚本是需要下载插件的,我选择了VScode,还是很好用的。
    最后我们将其添加为Menu场景的Canvas的组件(添加组件->脚本组件->menu),并在BtnP2C节点上添加按钮监听响应。


    这样,按钮监听就完成了。现在我们在Menu场景里点击一下人机按钮就会跳转到游戏场景了。
    2.3 预制资源预制资源一般是在场景里面创建独立的子界面或子窗口,即预制资源是存放在资源中,并不是节点中,例如本节课中的棋子。
    现在我们就来学习一下如何制作预制资源。

    再将black节点改名为Chess拖入下面Prefab文件夹使其成为预制资源。
    这其中,SpriteFrame 是核心渲染组件 Sprite 所使用的资源,设置或替换 Sprite 组件中的 spriteFrame 属性,就可以切换显示的图像,将其去掉防止图片被预加载,即棋子只是一个有大小的节点不显示图片也就没有颜色。
    2.4 结束场景直接在Game场景上制作结束场景。

    2.5 游戏脚本制作人机对战算法参考了这里https://blog.csdn.net/onezeros/article/details/5542379
    具体步骤便是初始化棋盘上225个棋子节点,并为每个节点添加事件,点击后动态显示棋子图片。
    代码如下:
    cc.Class({ extends: cc.Component, properties: { overSprite:{ default:null, type:cc.Sprite, }, overLabel:{ default:null, type:cc.Label }, chessPrefab:{//棋子的预制资源 default:null, type:cc.Prefab }, chessList:{//棋子节点的集合,用一维数组表示二维位置 default: [], type: [cc.node] }, whiteSpriteFrame:{//白棋的图片 default:null, type:cc.SpriteFrame }, blackSpriteFrame:{//黑棋的图片 default:null, type:cc.SpriteFrame }, touchChess:{//每一回合落下的棋子 default:null, type:cc.Node, visible:false//属性窗口不显示 }, gameState:'white', fiveGroup:[],//五元组 fiveGroupScore:[]//五元组分数 }, //重新开始 startGame(){ cc.director.loadScene("Game"); }, //返回菜单 toMenu(){ cc.director.loadScene("Menu"); }, onLoad: function () { this.overSprite.node.x = 10000;//让结束画面位于屏幕外 var self = this; //初始化棋盘上225个棋子节点,并为每个节点添加事件 for(var y = 0;y<15;y++){ for(var x = 0;x < 15;x++){ var newNode = cc.instantiate(this.chessPrefab);//复制Chess预制资源 this.node.addChild(newNode); newNode.setPosition(cc.p(x*40+20,y*40+20));//根据棋盘和棋子大小计算使每个棋子节点位于指定位置 newNode.tag = y*15+x;//根据每个节点的tag就可以算出其二维坐标 newNode.on(cc.Node.EventType.TOUCH_END,function(event){ self.touchChess = this; if(self.gameState === 'black' && this.getComponent(cc.Sprite).spriteFrame === null){ this.getComponent(cc.Sprite).spriteFrame = self.blackSpriteFrame;//下子后添加棋子图片使棋子显示 self.judgeOver(); if(self.gameState == 'white'){ self.scheduleOnce(function(){self.ai()},1);//延迟一秒电脑下棋 } } }); this.chessList.push(newNode); } } //开局白棋(电脑)在棋盘中央下一子 this.chessList[112].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.gameState = 'black'; //添加五元数组 //横向 for(var y=0;y<15;y++){ for(var x=0;x<11;x++){ this.fiveGroup.push([y*15+x,y*15+x+1,y*15+x+2,y*15+x+3,y*15+x+4]); } } //纵向 for(var x=0;x<15;x++){ for(var y=0;y<11;y++){ this.fiveGroup.push([y*15+x,(y+1)*15+x,(y+2)*15+x,(y+3)*15+x,(y+4)*15+x]); } } //右上斜向 for(var b=-10;b<=10;b++){ for(var x=0;x<11;x++){ if(b+x<0||b+x>10){ continue; }else{ this.fiveGroup.push([(b+x)*15+x,(b+x+1)*15+x+1,(b+x+2)*15+x+2,(b+x+3)*15+x+3,(b+x+4)*15+x+4]); } } } //右下斜向 for(var b=4;b<=24;b++){ for(var y=0;y<11;y++){ if(b-y<4||b-y>14){ continue; }else{ this.fiveGroup.push([y*15+b-y,(y+1)*15+b-y-1,(y+2)*15+b-y-2,(y+3)*15+b-y-3,(y+4)*15+b-y-4]); } } } }, //电脑下棋逻辑 ai:function(){ //评分 for(var i=0;i<this.fiveGroup.length;i++){ var b=0;//五元组里黑棋的个数 var w=0;//五元组里白棋的个数 for(var j=0;j<5;j++){ this.getComponent(cc.Sprite).spriteFrame if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.blackSpriteFrame){ b++; }else if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.whiteSpriteFrame){ w++; } } if(b+w==0){ this.fiveGroupScore[i] = 7; }else if(b>0&&w>0){ this.fiveGroupScore[i] = 0; }else if(b==0&&w==1){ this.fiveGroupScore[i] = 35; }else if(b==0&&w==2){ this.fiveGroupScore[i] = 800; }else if(b==0&&w==3){ this.fiveGroupScore[i] = 15000; }else if(b==0&&w==4){ this.fiveGroupScore[i] = 800000; }else if(w==0&&b==1){ this.fiveGroupScore[i] = 15; }else if(w==0&&b==2){ this.fiveGroupScore[i] = 400; }else if(w==0&&b==3){ this.fiveGroupScore[i] = 1800; }else if(w==0&&b==4){ this.fiveGroupScore[i] = 100000; } } //找最高分的五元组 var hScore=0; var mPosition=0; for(var i=0;i<this.fiveGroupScore.length;i++){ if(this.fiveGroupScore[i]>hScore){ hScore = this.fiveGroupScore[i]; mPosition = (function(x){//js闭包 return x; })(i); } } //在最高分的五元组里找到最优下子位置 var flag1 = false;//无子 var flag2 = false;//有子 var nPosition = 0; for(var i=0;i<5;i++){ if(!flag1&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); } if(!flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame != null){ flag1 = true; flag2 = true; } if(flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); break; } } //在最最优位置下子 this.chessList[this.fiveGroup[mPosition][nPosition]].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.touchChess = this.chessList[this.fiveGroup[mPosition][nPosition]]; this.judgeOver(); }, judgeOver:function(){ var x0 = this.touchChess.tag % 15; var y0 = parseInt(this.touchChess.tag / 15); //判断横向 var fiveCount = 0; for(var x = 0;x < 15;x++){ if((this.chessList[y0*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断纵向 fiveCount = 0; for(var y = 0;y < 15;y++){ if((this.chessList[y*15+x0].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右上斜向 var f = y0 - x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f+x < 0 || f+x > 14){ continue; } if((this.chessList[(f+x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右下斜向 f = y0 + x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f-x < 0 || f-x > 14){ continue; } if((this.chessList[(f-x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //没有输赢交换下子顺序 if(this.gameState === 'black'){ this.gameState = 'white'; }else{ this.gameState = 'black'; } }});
    这里便用到了节点监听,节点数组,定时器和动态更换sprite图片。
    新建Game脚本添加到ChessBoard节点下

    3 注意课程到这里就结束了,本课程部分资源来源于网络。
    第一次写技术分享,如果大家有什么好的建议和问题请在下方留言区留言。
    7 留言 2018-11-21 20:32:10 奖励20点积分
  • 深度学习 20、CNN

    前文链接:https://write-bug.com/article/2575.html
    卷积神经网络CNN上节我们介绍了DNN的基本网络结构和演示,其网络结构为:

    DNN BP神经网络实行全连接特性,参数权值过多,需求大量样本,计算困难,所以CNN利用局部连接、权值共享来实现减少权值的尝试,即局部感知野和权值共享。擅长图像分类问题,识别二维图形,现在也可以用于文本处理。

    CNN的隐藏层分为卷积层conv、和池化层pool,一般CNN结构(LeNet)依次为:

    INPUT->[[CONV -> RELU]N -> POOL?]M -> [FC -> RELU]*K->FC经典LeNet-5结构:最早用于数字识别的CNN


    卷积层Convolitions:
    用卷积层实现对图片局部的特征提取,窗口模式卷积核形式提取特征,映射到高维平面。
    两个关键性操作:

    局部关联:每个神经元看为一个滤波器filter
    窗口滑动:filter对局部数据计算

    局部感受野:
    1000 * 1000 图像 1000 * 1000神经元全连接: 1000 * 1000 * 1000 * 1000 = 10^12 个参数
    局部感受野:每个神经元只需要知道10 * 10区域,训练参数降到100M

    权值共享:
    所有神经元用同一套权重值,100M降到100,这样得到一个feature map。100种权重值100个map,一共需要训练100*100 = 10k参数

    卷积的计算:

    窗口滑动,我们可以想象一个窗口(卷积核feature_map),设置固定的feature_size,里面是固定的权值,这个窗口在同一张图片上从左到右从上到下游走,每次固定步长,从而得到更小size的矩阵。

    这里有两个神经元的卷积层,步长stride设置为2,边缘灰色部分为填充值zero-padding,防止窗口移到边缘时不够滑动遍历所有像素,而这里的神经元个数就是卷积层深度depth。
    在卷积层中每个神经元连接数据窗的权重是固定的,每个神经元只关注一个特性。神经元就是图像处理中的滤波器,比如边缘检测专用的Sobel滤波器,即卷积层的每个滤波器都会有自己所关注一个图像特征,比如垂直边缘,水平边缘,颜色,纹理等等,这些所有神经元加起来就好比就是整张图像的特征提取器集合。

    需要估算的权重个数减少: AlexNet 1亿 => 3.5w
    一组固定的权重和不同窗口内数据做内积: 卷积卷积理解:后面输出结果的前提是依赖前面相应权值积累,比如连续吃三天药身体才好,那么这三天的相关性是不同的权值

    激活函数把卷积层输出结果做非线性映射。CNN采用的激励函数一般为ReLU(The Rectified Linear Unit/修正线性单元),它的特点是收敛快,求梯度简单,但较脆弱,图像如下。

    激励层的实践经验:

    不要用sigmoid!不要用sigmoid!不要用sigmoid!首先试RELU,因为快,但要小心点如果2失效,请用Leaky ReLU或者Maxout某些情况下tanh倒是有不错的结果,但是很少
    池化层pooling:对特征进一步缩放
    池化层夹在连续的卷积层中间,用于压缩数据和参数的量,减小过拟合,压缩图像。
    特点:

    特征不变性:图像缩小还可以认出大概
    特征降维:去除冗余信息,防止过拟合


    池化层用的方法有Max pooling 和 average pooling,而实际用的较多的是Max pooling。
    这里就说一下Max pooling,其实思想非常简单。

    对于每个2*2的窗口选出最大的数作为输出矩阵的相应元素的值,比如输入矩阵第一个2*2窗口中最大的数是6,那么输出矩阵的第一个元素就是6,如此类推。
    平移不变性

    旋转不变性

    缩放不变性

    全连接层FC
    和BP神经网络一样,两层之间互相全连接,即把压缩后的维度打平为一组向量,再通过dnn的方法进行分类处理。
    Droupout
    正则化方法,CNN中解决过拟合问题,同时通用与其他网络。

    随机在全连接层,剪掉一些神经元,防止过拟合。
    卷积网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式,只要用已知的模式对卷积网络加以训练,网络就具有输入输出对之间的映射能力。
    CNN一个非常重要的特点就是头重脚轻(越往输入权值越小,越往输出权值越多),呈现出一个倒三角的形态,这就很好地避免了BP神经网络中反向传播的时候梯度损失得太快。
    卷积神经网络CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。由于CNN的特征检测层通过训练数据进行学习,所以在使用CNN时,避免了显式的特征抽取,而隐式地从训练数据中进行学习;再者由于同一特征映射面上的神经元权值相同,所以网络可以并行学习,这也是卷积网络相对于神经元彼此相连网络的一大优势。卷积神经网络以其局部权值共享的特殊结构在语音识别和图像处理方面有着独特的优越性,其布局更接近于实际的生物神经网络,权值共享降低了网络的复杂性,特别是多维输入向量的图像可以直接输入网络这一特点避免了特征提取和分类过程中数据重建的复杂度。
    卷积神经网络之训练算法:

    同一般机器学习算法,先定义Loss function,衡量和实际结果之间差距
    找到最小化损失函数的W和b, CNN中用的算法是SGD(随机梯度下降)

    卷积神经网络之优缺:

    优点

    共享卷积核,对高维数据处理无压力无需手动选取特征,训练好权重,即得特征分类效果好
    缺点

    需要调参,需要大样本量,训练最好要GPU物理含义不明确(也就说,我们并不知道没个卷积层到底提取到的是什么特征,而且神经网络本身就是一种难以解释的“黑箱模型”)

    卷积神经网络之典型CNN:

    LeNet,这是最早用于数字识别的CNN
    AlexNet, 2012 ILSVRC比赛远超第2名的CNN,比LeNet更深,用多层小卷积层叠加替换单大卷积层。
    ZF Net, 2013 ILSVRC比赛冠军
    GoogLeNet, 2014 ILSVRC比赛冠军
    VGGNet, 2014 ILSVRC比赛中的模型,图像识别略差于GoogLeNet,但是在很多图像转化学习问题(比如object detection)上效果奇好pytorch网络结构:

    class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 20, 5, 1) self.conv2 = nn.Conv2d(20, 50, 5, 1) self.fc1 = nn.Linear(4*4*50, 500) self.fc2 = nn.Linear(500, 10) def forward(self, x): print("x size: ", x.size()) x = F.relu(self.conv1(x)) print("conv1 size: ", x.size()) x = F.max_pool2d(x, 2, 2) print("pool size: ", x.size()) x = F.relu(self.conv2(x)) print("conv2 size: ", x.size()) x = F.max_pool2d(x, 2, 2) print("pool size: ", x.size()) x = x.view(-1, 4*4*50) print("view size: ", x.size()) x = F.relu(self.fc1(x)) print("fc1 size: ", x.size()) x = self.fc2(x) print("fc2 size: ", x.size()) sys.exit() return F.log_softmax(x, dim=1)
    1 留言 2019-06-11 11:42:43 奖励13点积分
  • 内核双机调试——追踪并分析内核API函数的调用 精华

    在64位Win10主机上调试32位Win7虚拟机内核,查看Win7内核中的函数调用关系,从而分析API函数实现的具体原理和流程。
    一、环境配置前提,对Win7虚拟机设置了调试的COM端口:\\.\pipe\com_1,波特率设置为115200,那么接下来就可以使用WinDbg来进行调试了。具体步骤如下所示:
    首先,在Win10上“以管理员身份”运行64位的WinDbg,点击File—>Kernel Debug—>COM,波特率设置为115200,端口与上面对应\\.\pipe\com_1,并勾选pipe、Reconnect,点击确定即可。

    如果我们不动它,WinDbg命令窗口将会一直显示“Waiting to reconnect…”的信息!这时,我们需要点击下工具栏上“Break”按钮,让Win7系统断下来,这样我们才可以用WinDbg进行调试,输入控制指令。


    接着,设置符号路径:File—>Symbol File Path—>输入:
    srv*c:\symbols*https://msdl.microsoft.com/download/symbols确定即可,这样WinDbg就会自动根据链接下载符号表到本地上。

    等待一会儿,即可下载完毕,方可输入指令。
    二、使用WingDbg追踪API调用流程接下来,我们以分析内核API函数NtQueryDirectoryFile为例,介绍WinDbg调试软件的使用方法。
    输入指令:uf nt!NtQueryDirectoryFile

    大概可以看出,NtQueryDirectoryFile函数的实现主要是调用了nt!BuildQueryDirectoryIrp函数构造查询文件的IRP以及nt!IopSynchronousServiceTail发送IRP来实现的。为了验证我们的想法,我们继续对这两个函数进行查看。
    输入指令:uf nt!BuildQueryDirectoryIrp

    从它函数实现调用了nt!IoAllocateIrp函数可知,我们的猜想是正确的。
    输入指令:uf nt!IopSynchronousServiceTail

    从它函数实现调用了nt!IofCallDriver函数可知,我们的猜想是正确的。
    nt!IofCallDriver函数定义如下所示:
    NTSTATUS IofCallDriver( PDEVICE_OBJECT DeviceObject, __drv_aliasesMem PIRP Irp);
    该函数的功能就是将IRP发送到指定的设备对象中处理,第1个参数就是处理IRP的设备对象。
    所以,接下来,我们继续用WinDbg分析上述nt!IopSynchronousServiceTail函数将IRP发给了哪个驱动设备进行处理的。
    输入指令:bu nt!NtQueryDirectoryFile
    输入指令:bl;查看所有断点
    输入指令:g;继续往下执行
    下断点,只要系统执行到nt!NtQueryDirectoryFile这个函数就会停下来。

    输入指令:u @eip
    输入指令:r;查看寄存器

    由于是对nt! NtQueryDirectoryFile这个函数下断点,所以现在停下来,指令指针eip指向的就是nt! NtQueryDirectoryFile函数的入口地址。此时,uf @eip就是反汇编nt! NtQueryDirectoryFile函数的内容。
    输入指令:bp 84043fdc
    输入指令:g

    84043fdc就是nt!IopSynchronousServiceTail函数的入口地址,断点会自动在此处断下。我们一步步下断点,逼近最终我们需要下断点的nt!IofCallDriver函数,确保是由nt!NtQueryDirectoryFile函数内容实现调用的nt!IofCallDriver函数。
    输入指令:uf @eip

    输入指令:bp 83e4cd19
    输入指令:g

    83e4cd19是nt!IofCallDriver函数的入口地址,WinDbg断点断下后,就会自动停在此处。
    输入指令:u @eip
    输入指令:r

    IofCallDriver函数是FASTCALL类型的调用约定,所以第1个参数的值存储在寄存器ecx中,即873f3508。所以,我们继续查看下该设备对象的结构数据。
    输入指令:dt nt!_DEVICE_OBJECT @ecx

    这时,我们边可以获取到驱动对象的地址DriverObject(0x86f5e670 _DRIVER_OBJECT),继续查看驱动对象的数据内容。
    输入指令:dt nt!_DRIVER_OBJECT 0x86f5e670

    由DriverName中我们可以看出,nt!NtQueryDirectoryFile是将IRP请求包发送给FltMgf驱动程序来处理的。
    3 留言 2019-09-01 09:25:57 奖励20点积分
  • 深度学习 19、DNN

    前文链接:https://write-bug.com/article/2540.html
    深度学习在前几节介绍的模型都属于单点学习模型,比如朴素贝叶斯的模型为概率,lr与softmax的模型为w,b权重,聚类kmeans的模型为中心向量点,还有后面的决策树的树模型等等都属于单点学习模型,都有各自的用途,而这里的深度学习来说,属于机器学习的一部分,可用不同的网络结构来解决各个领域的问题,模型为深度学习网络。
    通过前几节的学习,不知道大家有没有对单点学习的模型有感触,所谓模型是什么?
    我们从已有的例子(训练集)中发现输入x与输出y的关系,这个过程是学习(即通过有限的例子发现输入与输出之间的关系),而我们使用的function就是我们的模型,通过模型预测我们从未见过的未知信息得到输出y,通过激活函数(常见:relu,sigmoid,tanh,swish等)对输出y做非线性变换,压缩值域,而输出y与真实label的差距就是误差error,通过损失函数再求取参数偏导,梯度下降降低误差,从而优化模型,我们前面见过的损失函数是mse和交叉熵

    而DNN的模型就是网络结构,在说如何选取更好的网络结构之前,我们先介绍下神经网络:

    人类大脑的神经网络如上图具象出来就是无数个神经元,而神经元分为几个结构:

    树突:接收信号源
    神经元:核心计算单元
    轴突:传输数据到下一个神经元

    通过一个个神经元的传输,生成结果,比如说条件反射:缩手
    而在我们模型中,这个网络怎么表示呢?

    感知机:最最简单的神经网络,只有一个神经元


    树突:样本特征a1
    轴突:权重w1

    树突与轴突,也就是样本特征和权重相乘,有些类似lr的wx,共同决策了神经元的输入

    神经元:对信号进一步处理,比如lr中wx的∑,并加入偏置b,默认为1
    传递函数(激活函数):对f(s)做压缩非线性变换

    假设一个二分类问题:

    输入:
    树突P1:颜色
    树突P2:形状
    香蕉(黄色,弯形):(-1,-1)
    苹果(红色,圆形):(1,1)

    相当于坐标轴的两个点,我们需要一个分类面把两个点分割开。

    P1颜色的取值为1,-1
    P2形状的取值为1,-1
    初始化w1=w2=1,b=0

    则有:
    s=p1w1+p2w2=2>0s=p2 2w2 2+p2 2w2 2=-2 < 0这个初始化的参数可以区分,但是初始化有随机性,换一组参数就不能区分了
    所以感知机的训练方法,目的就是训练权重和偏置
    w=w+epb=b+ee(误差)=t-a=期望-实际
    如,假设w1=1,w2=-1,b=0

    苹果s=0
    期望为1,e=1-0=1

    修正:
    w1=1+1*1=2w2=-1+1*1=0b=0+1=1s=3>0为苹果但是,y=wx+b只能解决线性可分的问题,对于异或问题,也就是四点对立面,需要一个曲线做分割,而这就意味着需要多层神经元和信号源共同决策分割面,也就形成了复杂的神经网络,而目标:同样是那么多边的权重。

    通过不同层间的结果作为下一层的输入,再对输入中间结果进一步处理,以此类推形成足够的深度,对于大部分问题来说都可以提到一个提升效果的作用。
    感知机只有从输出层具有激活函数,即功能神经元,这里的隐层和输出层都是具有激活函数的功能神经元。
    层与层全连接,同层神经元不连接,不跨层连接的网络又称为多层前馈神经网络。
    前馈:网络拓扑结构上不存在环和回路
    我们通过pytorch实现演示:
    二分类问题:
    假数据准备:
    # make fake data#正态分布随机产生n_data = torch.ones(100, 2)x0 = torch.normal(2*n_data, 1) # class0 x data (tensor), shape=(100, 2)y0 = torch.zeros(100) # class0 y data (tensor), shape=(100, 1)x1 = torch.normal(-2*n_data, 1) # class1 x data (tensor), shape=(100, 2)y1 = torch.ones(100) # class1 y data (tensor), shape=(100, 1)#拼接数据x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # shape (200, 2) FloatTensor = 32-bit floatingy = torch.cat((y0, y1), ).type(torch.LongTensor) # shape (200,) LongTensor = 64-bit integer# tensor类型 :张量:高维特征,不可更新#variable类型:可变参数,更新w,b从而更新xy# torch can only train on Variable, so convert them to Variablex, y = Variable(x), Variable(y)设计构建神经网络:class net 继承torch.nn.model
    200\*2, 2\*10, 10\*2参数:n_fea=2,n_hidden=10,n_output=2
    输入层2
    初始化hidden层10
    self.hidden=torch.nn.linear(n_fea,n_hidden)输出层2
    self.out=torch.nn.linear(n_hidden,n_output)class Net(torch.nn.Module): def __init__(self, n_feature, n_hidden, n_output): super(Net, self).__init__() self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer self.out = torch.nn.Linear(n_hidden, n_output) # output layer#激活函数,在隐藏层后加relu非线性变换 def forward(self, x): x = F.relu(self.hidden(x)) # activation function for hidden layer x = self.out(x) return x #输出的预测值xnet = Net(n_feature=2, n_hidden=10, n_output=2) # define the networkprint(net)参数梯度下降sgd:
    optimizer = torch.optim.sgd(net.parameters(),lr=0.02)损失函数:交叉熵:负对数似然函数,并计算softmax
    loss_func = torch.nn.CrossEntropyLoss()参数初始化:
    optimizer.zero_grad()反向传播:
    loss.backward()计算梯度:
    optimizer.step()for t in range(100): out = net(x) # input x and predict based on x loss = loss_func(out, y) # must be (1. nn output, 2. target), the target label is NOT one-hotted optimizer.zero_grad() # clear gradients for next train loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients if t % 2 == 0: # plot and show learning process plt.cla() prediction = torch.max(F.softmax(out), 1)[1] pred_y = prediction.data.numpy().squeeze() target_y = y.data.numpy() plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn') accuracy = sum(pred_y == target_y)/200. plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'}) plt.pause(0.1)plt.ioff()plt.show()交叉熵问题:
    二次代价:

    a 是 神经元的输出,其中 a = σ(z), z = wx + b
    C求导:w,b求偏导:
    受到激活函数影响

    交叉熵函数:


    不用对激活函数导数影响,不管是对w求偏导还是对b求偏导,都和e有关,而和激活函数的导数无关,比如说sigmoid函数,只有中间下降最快,两边的下降逐渐缓慢,最后斜率逼近于直线
    反向传播算法:errorBP
    可应用于大部分类型神经网络
    一般说BP算法时,多指多层前馈神经网络
    上面的数据为了演示,造的假数据形成200*2维的矩阵,一列label
    那么如何对接我们的真实数据呢?

    在我们前几节的数据中大都是这种形式,label feature_id:score
    定义一个数组
    output =[None]*3 维护3列tensor数据:
    max_fid = 123num_epochs = 10data_path = '/root/7_codes/pytorch_test/data/a8a'all_sample_size = 0sample_buffer = []with open(data_path, 'r') as fd: for line in fd: all_sample_size += 1 sample_buffer.append(line.strip())output = [None] *3#idids_buffer = torch.LongTensor(all_sample_size, max_fid)ids_buffer.fill_(1)output[0] = ids_buffer#weightweights_buffer = torch.zeros(all_sample_size, max_fid)output[1] = weights_buffer#labellabel_buffer = torch.LongTensor(all_sample_size)output[2] = label_bufferrandom.shuffle(sample_buffer)sample_id = 0for line in sample_buffer: it = line.strip().split(' ') label = int(it[0]) f_id = [] f_w = [] for f_s in it[1:]: f, s = f_s.strip().split(":") f_id.append(int(f)) f_w.append(float(s)) #解析数据后填入数组 f_id_len = len(f_id)#适当拓展数组大小 output[0][sample_id, 0:f_id_len] = torch.LongTensor(f_id) output[1][sample_id, 0:f_id_len] = torch.FloatTensor(f_w) output[2][sample_id] = label sample_id += 1fid, fweight, label = tuple(output)fid, fweight, label = map(Variable, [fid, fweight, label])接下来是设计网络结构:

    def emb_sum(embeds, weights): emb_sum = (embeds * weights.unsqueeze(2).expand_as(embeds)).sum(1) return emb_sum#输入结构为22696条数据*124维特征*32维特征向量:可以将其想象成立方体的三维条边#想要把id的特征向量和对应的score相乘并相加,需要把score也就是权重weight拓展成和id同样的维度,即22696 *124 *32 ,而sum(1)就是把124维相加成1维,最后形成22696×32维的平面矩阵,即每个样本后有个32维的向量存在,相当于把feature_id向量化class Net(torch.nn.Module): def __init__(self, n_embed, n_feature, n_hidden, n_output): super(Net, self).__init__() self.embedding = nn.Embedding(max_fid + 1, n_feature) #124特征*32隐层向量 self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer self.out = torch.nn.Linear(n_hidden, n_output) # output layer def forward(self, fea_ids, fea_weights): embeds = self.embedding(fea_ids) embeds = emb_sum(embeds, fea_weights)#对特征进一步处理,融合id与weight_score进行相容 embeds = nn.functional.tanh(embeds)#非线性变换 hidden = self.hidden(embeds) output = self.out(hidden) return outputnet = Net(n_embed = 32, n_feature= 32, n_hidden=10, n_output=2) # define the networkprint(net)以上,我们对接了真实的数据类型,那这个demo有没有什么问题呢?
    1.是否支持大规模数据,进行快速训练?mini-batch
    我们之前学BGD、SGD、MGD梯度下降的训练方法,在上面就运用了sgd的方法,不管是BGD还是SGD都是对所有样本一次性遍历一次,如果想提升,大致相当于MGD的方法:
    把所有样本分批处理,每批次有多少个样本(batch),循环所有样本循环多少轮(epoch)。
    每训练一个batch产生一条log,参数可保存,之前是每epoch输出一次log,这种方法训练快,性能虽然赶不上全局跑出来的准确率,但仍然一直逼近,小步快跑,每次加载到内存的数据较小,性能方面就高。
    num_epochs=10batch_size =1000data_path = './data/a8a'max_fid = 123def read_and_shuffle(filepath,shuffle=True): lines=[] with open(filepath,’r’) as fd: for linr in fd: lines.append(line.strip()) if shuffle: random.shuffle(lines) return linesclass DataLoader(object): def __init__(self,data_path,batch_size,shuffle = True): self.batch_size = batch_size self.shuffle = shuffle if os.path.isdir(data_path): self.files = [ join_path(data_path,i) for I in os.listdir(data_path)] else: self.files = [data_path] def parse(self,line_ite): for line in line_ite: it = line.strip().split(‘ ’) label = int(it[0]) f_id = [] f_w = [] for f_s in it[1:]: f,s = f_s.strip().split(‘:’) f_id.append(int(f)) f_w.append(float(s)) f_id_len = len(f_id) output = [] output.append(f_id) output.append(f_w) output.append(f_id_len) output.append(label) yield output def to_tensor(self,batch_data,sample_size): output =[None]*3 ids_buffer = torch.LongTensor(sample_size,max_fid) ids_buffer.fill_(1) output[0]=ids_buffer weights_buffer = torch.zeros(sample_size,max_fid) output[1]=weights_buffer label_buffer = torch.LongTensor(sample_size) output[2] = label_buffer for sample_id,sample in enumerate(batch_data): f_id,f_weight,f_id_len,label = sample output[0][sample_id,0:f_id_len] = torch.LongTensor(f_id) output[1][sample_id,0:f_id_len] = torch.FloatTensor(f_w) output[2][sample_id] = label return tuple(output) def batch(self): count = 0 batch_buffer =[] for f in self.files: for parse_res in self.parse(read_and_shuffle(f,self.shuffle)): count+=1 batch_buffer.append(parse_res) if count ==self.batch_size: t_size = len(batch_buffer) batch_tensor = self.to_tensor(batch_buffer,t_size) yield (batch_tensor,t_size) count = 0 batch_buffer = [] if batch_buffer: t_size = len(batch_buffer) batch_tensor = self.to_tensor(batch_buffer,t_size) yield (batch_tensor,t_size)for epoch in range(1,num_epochs+1): for (batch_id,data_tuple) in enumrate(dataloader.batch(),1): data = data_tuple[0] all_sample_size = data_tuple[1] fid,fweight,label =data fid,fweight,label=map(Variable,[fid,fweight,label] optimizer = torch.optim.SGD(net.parameters(), lr=0.02) loss_func = torch.nn.CrossEntropyLoss() # the target label is NOT an one-hotted out = net(fid, fweight) optimizer.zero_grad() # clear gradients for next train loss = loss_func(out, label) loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients if batch_id % 2 == 0:#每两批次打印一次 # plot and show learning process prediction = torch.max(F.softmax(out), 1)[1] pred_y = prediction.data.numpy().squeeze() target_y = label.data.numpy() accuracy = sum(pred_y == target_y)/float(all_sample_size) print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy)2.怎么使用模型?
    现在模型可以训练自己数据了,效果也提升了,那如何把模型的参数保存下来?
    print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy) checkpoint = { 'model': net.state_dict(),#网络静态参数 'epoch': epoch, 'batch': batch_id, } model_name = 'epoch_%s_batch_id_%s_acc_%s.chkpt' % (epoch, batch_id, accuracy) model_path = join_path('./model', model_name) torch.save(checkpoint, model_path)拆分上面大文件出各个参数:

    import osimport sysimport torchimport torch.nn as nnimport randomfrom torch.autograd import Variablefrom os.path import join as join_pathimport torch.nn.functional as Fmodel_path = sys.argv[1]checkpoint = torch.load(model_path)model_dict = checkpoint['model']for k, v in model_dict.items(): print k model_sub_file_name = k model_sub_file_path = join_path('./dump', model_sub_file_name) f = open(model_sub_file_path, 'w') value = model_dict[k].cpu().numpy() value.dump(f) f.close()模型怎么用?加载、预测
    import sysimport numpy as npimport mathembedding_weight = np.load('dump/embedding.weight')hidden_weight = np.load('./dump/hidden.weight')hidden_bias = np.load('./dump/hidden.bias')out_weight = np.load('./dump/out.weight')out_bias = np.load('./dump/out.bias')emb_dim = embedding_weight.shape[1]def calc_score(fids, fweights): embedding = np.zeros(emb_dim) for id, weight in zip(fids, fweights): embedding += weight * embedding_weight[id] embedding_tanh = np.tanh(embedding)#对应相乘,第二维相加成10维向量 hidden = np.sum(np.multiply(hidden_weight, embedding_tanh), axis=1) + hidden_bias out = np.sum(np.multiply(out_weight, hidden), axis=1) + out_bias return outdef softmax(x): exp_x = np.exp(x) softmax_x = exp_x / np.sum(exp_x) return softmax_xfor line in sys.stdin: ss = line.strip().split(' ') label = ss[0].strip() fids = [] fweights = [] for f_s in ss[1:]: f, s = f_s.strip().split(':') fids.append(int(f)) fweights.append(float(s)) pred_label = softmax(calc_score(fids, fweights))[1] print label, pred_label3.怎么评估?auc
    cat auc.raw | sort -t$'\t' -k2g | awk -F'\t' '($1==-1){++x;a+=y}($1==1){++y}END{print 1.0 - a/(x*y)}'acc=0.827auc=0.842569acc=0.745auc=0.494206轮数、acc都影响着auc,数字仅供参考
    总结以上,是以二分类为例,从头演示了一遍神经网络,大家可再找一些0-9手写图片分类任务体验一下,这里总结下,模型拥有参数,所以涉及参数初始化,有了参数后涉及正向传递,分为拟合和泛化两部分建立好模型后,学习过程就是确定参数的过程,使用的是梯度下降法,定义一个误差函数loss function 来衡量模型预测值与真实值(label)之间的差距,通过不断降低差距来使模型预测值逼近真实值,更新参数的反向传递是利用对应的误差值来求得梯度(batch、epoch、learning rate 、shuffle),评估标准也根据任务类型和具体要求可以使用更多的指标。
    回归任务常用均方差MSE作为误差函数。评估可用均方差表示。
    分类任务常用交叉熵(cross entropy)和softmax配合使用。评估可用准确率表示。
    回归任务:

    借用知乎yjango末尾的一句话:
    但最重要的并非训练集的准确率,而是模型从未见过的测试集的准确率(泛化能力),正如高考,真正的目的是为了解出从未见过的高考题,而不是已经做过的练习题,学习的困难之处正是要用有限的样本训练出可以符合无限同类样本规律的模型。是有限同无限的对抗,你是否想过,为什么看了那么多的道理、听了那么多人生讲座,依然过不哈这一生,一个原因就在于演讲者向你展示的例子都是训练集的样本,他所归纳的方法可以很轻松的完美拟合这些样本,但未必对你将面临的新问题同样奏效,人的一生都在学习,都在通过观察有限的例子找出问题和答案的规律,中医是,玄学是,科学同样是,但科学,是当中最为可靠的一种。
    1 留言 2019-06-05 13:46:30 奖励18点积分
  • windows下静态使用QxOrm框架并使用泛型编程 (一)

    开发环境
    Qt 5.6 for Desktop
    QxOrm版本为1.46 下载链接https://github.com/QxOrm/QxOrm

    下载完成以后QxOrm文件夹里会有简单的说明文档doc/index.html 不过有些方法是比较过时的,于是我测试了许久才测试好的框架代码。
    下载完成后打开QtCreator,选择新建项目选择 其他项目/子目录项目 类似下图的建立

    然后进入该项目的文件夹路径即200-300.pro所在路径 将QxOrm整个文件夹复制进来
    接着在200-300.pro文件中改为
    TEMPLATE = subdirsSUBDIRS += \ QxOrm这样就可以看到QxOrm项目成功加入到整体项目中了。而程序需要主项目作为运行所以我们右键200-300新建一个Application/Qt Widgets Application 我这边就叫Demo好了 。接着右键Demo添加库/外部库 库文件选择QxOrm/lib/libQxOrm.a 链接选择静态然后点下一步完成
    注意:需要在Demo.pro文件中加入QT += sql 这句话是将数据库加入到项目中去。
    目前环境就算是搭好了。下一篇继续讲如何使用。
    1 留言 2019-07-23 14:37:42 奖励8点积分
  • 【Cocos Creator实战教程(4)】——炸弹人(TiledMap相关) 精华

    1. 相关知识点
    虚拟手柄(新)
    地图制作
    碰撞检测
    动画制作

    2. 步骤2.1 制作地图2.1.1.新建19x19的地图,Tile大小32x32,导入图块资源2.1.2 建立三个图层(ground,hide,main)和一个对象层(objects)ground是背景层,用绿色的草坪图块填充满

    hide是道具层,挑选道具图块(包括出口的门)放在几个位置

    main是障碍物层,还原经典炸弹人的地图布局,每隔一个位置放一个钢块,用土块覆盖道具层的道具,在其他位置再放几个土块

    objects层有两个对象(player,enemy)标记玩家和敌人的位置信息
    最终效果如图

    2.1.3 将除了草坪图块的其他图块添加type属性,属性值分别为,soil(土块),steel(钢块),door(门),prop1(道具1)…
    2.1.4 保存地图文件和图集文件放在一起2.2 绑定地图2.2.1 新建工程Bomberman,将所需资源全部导入,项目结构如下(有些是后来添加的)
    2.2.2 将地图拖入场景(自动生成map和layer节点),将锚点改为(0,0),下面出现的player等节点也都是(0,0)为锚点2.2.3 新建脚本game.js添加为map节点组件,声明全局变量,在属性面板中拖入相关层节点
    2.3 虚拟手柄2.3.1 我们准备了上下左右炸弹五个按钮的原始状态和按下状态共十张图片素材,在场景编辑器中看着顺眼的地方摆放这五个按钮(将label删除),并将按钮的相应状态的按钮图片添加到右侧属性面板中2.3.2 为每个按钮添加点击事件,(map->game->btnBomb, btnUp, btnDown, btnLeft, btnRight)
    2.4 炸弹动画2.4.1 将炸弹图片集中的任意一个拖入场景中,将其宽高改为32x32,再拖回资源管理器中制作一个炸弹的prefab bomb,为其添加Animation组件2.4.2 新建炸弹爆炸动画clip explode,拖入bomb的属性面板2.4.3 在动画播放完成的最后一帧加入一个帧事件,响应事件方法名为exploded
    2.5 主角和敌人2.5.1 添加player和enemy为map节点的两个子节点,锚点0,0,大小32x32 (位置随意,一会我们要在代码中动态设置他们的位置)
    3. 总结
    虚拟手柄是一个比较热点的控制,现实生活中还会有圆圈控制各个角度的,也有开源项目大家可以参考
    注意节点前后层的遮挡关系

    注意:本教程部分素材来源于网络,另请大家在评论区踊跃提问发言。
    7 留言 2018-11-24 23:12:26 奖励20点积分
  • 机器学习 23 、BM25 Word2Vec

    前文链接:https://write-bug.com/article/2689.html
    word embedding什么是word embedding? 简单的来说就是将一个特征转换为一个向量。在推荐系统当中,我们经常会遇到离散特征,如userid、itemid。对于离散特征,我们一般的做法是将其转换为one-hot,但对于itemid这种离散特征,转换成one-hot之后维度非常高,但里面只有一个是1,其余都为0。这种情况下,我们的通常做法就是将其转换为embedding。
    word embedding为什么翻译成词嵌入模型?一个向量可以想象成多维空间,那么在多维空间里面找到一个点来代表这个词,相当于把这个词嵌入到了多维空间里,所以可以称为词嵌入模型。
    one-hot编码形式
    输入:
    apple on a apple tree – Bag of Words: ["apple", "on", "a", "tree"]– Word Embedding的输出就是每个word的向量表示
    最简单直接的是one-hot编码形式:那么每个word都对应了一种数值表示
    例如,apple对应的vector就是[1, 0, 0, 0],a对应的vector就是[0, 0, 1, 0]
    两类表达方式:基于频率:(本质:one-hot)
    Count vector:需要穷举所有单词,形成非常庞大、稀疏的矩阵,穷举时,计算每词的词频TF
    每个文档用词向量的组合来表示,每个词的权重用其出现的次数来表示
    假设语料库内容如下:
    – D1: He is a boy. – D2: She is a girl, good girl.那么可以构建如下2 × 7维的矩阵

    tfidf vector:同时考虑了TF与IDF
    IDF=log(N/n)。 – N代表语料库中文档的总数 – n代表某个word在几个文档中出现过;
    Co-Occurrence vector:只考虑滑动窗口内单词的共现,等于考虑了上下文之间的关系
    前面两类方法,每个字都是独立的,缺少了语义的学习
    例如: He is not lazy. He is intelligent. He is smart.
    如果Context Window大小为2,那么可以得到如下的共现矩阵:

    he和is计算方法(窗口共现):

    基于模型:
    CBOW
    Skip-Gram
    技术升级TF-IDF-》BM25
    Co-Occurrence-》WordtoVector
    BM25:定位:搜索引擎相关性评分
    通常我们在搜索信息时,搜索的句子为query,关联出的排序列表为item,即:
    query -》item
    那么可分词query-》token1 token2 token3
    同时可把item -》doc-》token token token
    词在文章中的评分:token1 -》doc :score1(tfidf)
    所以相关性评分query-》item:score1+score2+…
    公式可表示为:
    S(q,d)=∑Wi*R(qi,d)Wi用idf表示:

    N为索引中的全部文档数
    n(qi)为包含了qi的文档数
    相关性评分R:


    k1,k2,b为调节因子,一般k1=2,b=0.75
    b是调整文档长度对相关性影响的大小 • b越大,文档长度影响R越大,反之越小


    fi为qi在d中的出现频率 =TF
    qfi为qi在Query中的出现频率 =query中的TF

    在之前的tfidf中TF越大最后的分数也越大,而通常情况下,文章越长,TF大的可能性越大,没有考虑到文章长度的问题,这里BM25考虑到了这个问题,如果文章过长就要给定一定的打压,那么如何界定文章长度呢?除以avgdl,即所有文章平均长度

    dl为文档d的长度
    avgdl为所有文档的平均长度

    公式简化:
    绝大部分情况下,qi在Query中只会出现一次,即qfi=1


    BM25实践:
    1.gensim word2vec
    语料库-》每个词的50维向量即word embedding
    from gensim.models import word2vecmodel=word2vec.Word2Vec(sentences,size=50,window=5,min_count=1,workers=6)model.wv.save_word2vec_format(file_voc, binary=False)2.coumpute_idf
    每个词的idf
    file_corpus='../data/file_corpus.txt'file_voc='../data/voc.txt'file_idf='../data/idf.txt'class ComIdf(object): def __init__(self,file_corpus,file_voc,file_idf): self.file_corpus=file_corpus self.file_voc=file_voc self.file_idf=file_idf self.voc=load_voc(self.file_voc) self.corpus_data=self.load_corpus() self.N=len(self.corpus_data) def load_corpus(self): input_data = codecs.open(self.file_corpus, 'r', encoding='utf-8') return input_data.readlines() def com_idf(self,word): n = 0 for _,line in enumerate(self.corpus_data): n+=line.count(word) idf=math.log(1.0*self.N/n+1) return {word:idf} def parts(self): words=set(self.voc.keys()) multiprocessing.freeze_support() cores=multiprocessing.cpu_count() pool=multiprocessing.Pool(processes=cores-2) reuslt=pool.map(self.com_idf,words) idf_dict=dict() for r in reuslt: k=list(r.keys())[0] v=list(r.values())[0] idf_dict[k]=idf_dict.get(k,0)+v with codecs.open(self.file_idf,'w',encoding='utf-8') as f:f.write(json.dumps(idf_dict,ensure_ascii=False,indent=2,sort_keys=False))if __name__ == '__main__': t1 = time.time() IDF=ComIdf(file_corpus,file_voc,file_idf)IDF.parts()3.get_sentence
    分割句子,取出最常见句子1w个
    def get_sentence(): file_corpus=codecs.open('../data/file_corpus.txt','r',encoding='utf-8') file_sentence=codecs.open('../data/file_sentence.txt','w',encoding='utf-8') st=dict() for _,line in enumerate(file_corpus): line=line.strip() blocks=re_han.split(line) for blk in blocks: if re_han.match(blk) and len(blk)>10: st[blk]=st.get(blk,0)+1 st=sorted(st.items(),key=lambda x:x[1],reverse=True) for s in st[:10000]: file_sentence.write(s[0]+'\n') file_corpus.close() file_sentence.close()get_sentence()4.test
    各种算法计算打分:cos\idf\bm25\jaccard
    file_voc='./data/voc.txt'file_idf='./data/idf.txt'file_userdict='./data/medfw.txt'class SSIM(object): def __init__(self): t1 = time.time() self.voc=load_voc(file_voc) print("Loading word2vec vector cost %.3f seconds...\n" % (time.time() - t1)) t1 = time.time() self.idf=load_idf(file_idf) print("Loading idf data cost %.3f seconds...\n" % (time.time() - t1)) jieba.load_userdict(file_userdict) def M_cosine(self,s1,s2): s1_list=jieba.lcut(s1) s2_list=jieba.lcut(s2) v1=np.array([self.voc[s] for s in s1_list if s in self.voc]) v2=np.array([self.voc[s] for s in s2_list if s in self.voc]) v1=v1.sum(axis=0) v2=v2.sum(axis=0) sim=1-spatial.distance.cosine(v1,v2) return sim def M_idf(self,s1, s2): v1, v2 = [], [] s1_list = jieba.lcut(s1) s2_list = jieba.lcut(s2) for s in s1_list: idf_v = self.idf.get(s, 1) if s in self.voc: v1.append(1.0 * idf_v * self.voc[s]) for s in s2_list: idf_v = self.idf.get(s, 1) if s in self.voc: v2.append(1.0 * idf_v * self.voc[s]) v1 = np.array(v1).sum(axis=0) v2 = np.array(v2).sum(axis=0) sim = 1 - spatial.distance.cosine(v1, v2) return sim def M_bm25(self,s1, s2, s_avg=10, k1=2.0, b=0.75): bm25 = 0 s1_list = jieba.lcut(s1) for w in s1_list: idf_s = self.idf.get(w, 1) bm25_ra = s2.count(w) * (k1 + 1) bm25_rb = s2.count(w) + k1 * (1 - b + b * len(s2) / s_avg) bm25 += idf_s * (bm25_ra / bm25_rb) return bm25 def M_jaccard(self,s1, s2): s1 = set(s1) s2 = set(s2) ret1 = s1.intersection(s2) ret2 = s1.union(s2) jaccard = 1.0 * len(ret1)/ len(ret2) return jaccard def ssim(self,s1,s2,model='cosine'): if model=='idf': f_ssim=self.M_idf elif model=='bm25': f_ssim=self.M_bm25 elif model=='jaccard': f_ssim=self.M_jaccard else: f_ssim = self.M_cosine sim=f_ssim(s1,s2) return simsm=SSIM()ssim=sm.ssimdef test(): test_data=[u'临床表现及实验室检查即可做出诊断', u'面条汤等容易消化吸收的食物为佳', u'每天应该摄入足够的维生素A', u'视患者情况逐渐恢复日常活动', u'术前1天开始预防性运用广谱抗生素'] model_list=['cosine','idf','bm25','jaccard'] file_sentence=codecs.open('./data/file_sentence.txt','r',encoding='utf-8') train_data=file_sentence.readlines() for model in model_list: t1 = time.time() dataset=dict() result=dict() for s1 in test_data: dataset[s1]=dict() for s2 in train_data: s2=s2.strip() if s1!=s2: sim=similarity.ssim(s1,s2,model=model) dataset[s1][s2]=dataset[s1].get(s2,0)+sim for r in dataset: top=sorted(dataset[r].items(),key=lambda x:x[1],reverse=True) result[r]=top[0] with codecs.open('./data/test_result.txt','a') as f: f.write('--------------The result of %s method------------------\n '%model) f.write('\tThe computing cost %.3f seconds\n'% (time.time() - t1)) f.write(json.dumps(result, ensure_ascii=True, indent=2, sort_keys=False)) f.write('\n\n') file_sentence.close()test()word2vec继承了之前的窗口滑动思想,得到更好的语义理解
    涉及的技术点:
    – Hierarchical softmax分层softmax
    – Negative sampling负采样
    两种方式:

    CBOW:根据前后词,预测中间词
    Skip-Gram :根据中间词预测前后词,工业用的更多

    CBOW一句话包含了很多单词,时间窗口选择到了一个中间词,w3,假设左边右边各学习两个,中间词假设不知道,那么根据w1245猜测中间这个w3词,故意装作w3不知道做预测,那么w3就是中心词label,w1245就是周围词训练数据,随着时间窗口滑动,同理w4又继续成为了中间词,那么由此类推形成了很多训练样本


    Input layer:由one-hot编码的输入上下文{x1,…,xC}组成, 其中窗口大小为C,词汇表大小为V
    Hidden layer:隐藏层是N维的向量
    Output layer:被one-hot编码的输出单词y

    被one-hot编码的输入向量通过一个V×N维的权重矩阵W连接到隐藏层; 隐藏层通过一个N×V的权重矩阵W′连接到输出层
    不管输入多少词,相加并都映射为中间得隐层向量上,再做一个softmax输出
    Skip-Gram:CBOW的网络翻转
    样本怎么得到的?

    input word和output word都是one-hot编码的向量。最终模型的输出是一个概率分布

    softmax分类需要所有单词:这里是50002个单词,需要遍历一遍,那么每次计算都需要一定时间,不管是多次还是一次时间成本都很高,所以引出了分层softmax:
    Hierarchical softmax哈夫曼树

    是二叉树的一种特殊形式,又称为最优二叉树,其主要作用在于数据压缩和编码长度的优化
    定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度(WPL)达到最小,称这样的二叉 树为最优二叉树,也称为霍夫曼树(Huffman Tree)
    WPL:树的带权路径长度,所有叶子结点的带权路径长度之和

    让wpl最小,压缩数据信息
    主要用于编码:从根节点出发,向左为0,向右为1
    等长编码和哈夫曼编码


    生成:
    每个节点按权重大小从小到大排序
    两个最小的为叶子节点相加成为父节点并和其他最小的成两个叶子节点,最终形成哈夫曼树
    之前做softmax,50002个单词需要从头到尾计算一遍,把词表做成树,每个叶子节点都是一个单词,树的每个结点都是一个sigmoid,来了一个单词做一个lr二分类向左还是向右分类直至最后叶子节点为所要的词
    Negative sampling负采样
    思想:每次让一个训练样本仅更新一小部分权重
    需要离错误的样本更远

    不对所有输出神经元对应的权值进行更新,只是随机选取几个“negative”词,更新他们对应的权重。当然, 我们也会更新”positive”的对应权值
    随机取的样本只要不共现就是负样本,概率大的会多选,即出现多的单词自然被取出的概率越大
    实际论文实现:


    CBOW:input是context(周围词)而output是中心词,训练过程中其实是在从output的loss学习周围词的信息 也就是embedding,但是在中间层是average的,一共预测V(vocab size)次就够了
    一次softmax

    Skipgram:用中心词预测周围词,预测的时候是一对word pair,等于对每一个中心词都有K个词作为output, 对于一个词的预测有K次,所以能够更有效的从context中学习信息,但是总共预测K*V词
    多次softmax

    Skip-Gram的训练时间更长,更准确
    Cbow训练速度快,Skip-Gram更好处理生僻字

    Word2Vec实践:
    import sysimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.utils.data as tudfrom torch.nn.parameter import Parameterfrom collections import Counterimport numpy as npimport randomimport mathimport pandas as pdimport scipyimport sklearnfrom sklearn.metrics.pairwise import cosine_similarity# 超参数K = 100 # number of negative samplesC = 3 # nearby words thresholdNUM_EPOCHS = 2 # The number of epochs of trainingMAX_VOCAB_SIZE = 30000 # the vocabulary sizeBATCH_SIZE = 128 # the batch sizeLEARNING_RATE = 0.2 # the initial learning rateEMBEDDING_SIZE = 100# 分词def word_tokenize(text): return text.split()with open("./data/text8", "r") as fin: text = fin.read()text = [w for w in word_tokenize(text.lower())]print(text[1])vocab = dict(Counter(text).most_common(MAX_VOCAB_SIZE - 1))#计数器,取前30000个高频元素vocab["<unk>"] = len(text) - np.sum(list(vocab.values()))print (np.sum(list(vocab.values())))print (len(text))print (vocab["<unk>"])print (len(vocab))#output:originated170052071700520769010330000idx_to_word = [word for word in vocab.keys()]word_to_idx = {word: i for i, word in enumerate(idx_to_word)}word_counts = np.array([count for count in vocab.values()], dtype=np.float32)word_freqs = word_counts / np.sum(word_counts)word_freqs = word_freqs ** (3. / 4.)word_freqs = word_freqs / np.sum(word_freqs) # 用来做 negative samplingVOCAB_SIZE = len(idx_to_word)print(VOCAB_SIZE)#text 每个单词#word_to_idx 单词id化:dict#idx_to_word 每个单词#word_freqs 负采样概率#word_counts 单词计数列表#数据样本列举:class WordEmbeddingDataset(tud.Dataset): def __init__(self, text, word_to_idx, idx_to_word, word_freqs, word_counts): ''' text: a list of words, all text from the training dataset word_to_idx: the dictionary from word to idx idx_to_word: idx to word mapping word_freq: the frequency of each word word_counts: the word counts ''' super(WordEmbeddingDataset, self).__init__() self.text_encoded = [word_to_idx.get(t, VOCAB_SIZE - 1) for t in text] self.text_encoded = torch.Tensor(self.text_encoded).long() self.word_to_idx = word_to_idx self.idx_to_word = idx_to_word self.word_freqs = torch.Tensor(word_freqs) self.word_counts = torch.Tensor(word_counts) def __len__(self): ''' 返回整个数据集(所有单词)的长度 ''' return len(self.text_encoded) def __getitem__(self, idx): ''' 这个function返回以下数据用于训练 - 中心词 - 这个单词附近的(positive)单词 - 随机采样的K个单词作为negative sample ''' center_word = self.text_encoded[idx] pos_indices = list(range(idx - C, idx)) + list(range(idx + 1, idx + C + 1)#窗口左3右3 pos_indices = [i % len(self.text_encoded) for i in pos_indices]#去除类似第一次左边无词这种情况 pos_words = self.text_encoded[pos_indices] neg_words = torch.multinomial(self.word_freqs, K * pos_words.shape[0], True) return center_word, pos_words, neg_wordsdataset = WordEmbeddingDataset(text, word_to_idx, idx_to_word, word_freqs, word_counts)dataloader = tud.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
    class EmbeddingModel(nn.Module): def __init__(self, vocab_size, embed_size): ''' 初始化输出和输出embedding ''' super(EmbeddingModel, self).__init__() self.vocab_size = vocab_size self.embed_size = embed_size initrange = 0.5 / self.embed_size self.out_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False) self.out_embed.weight.data.uniform_(-initrange, initrange) self.in_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False) self.in_embed.weight.data.uniform_(-initrange, initrange) def forward(self, input_labels, pos_labels, neg_labels): ''' input_labels: 中心词, [batch_size] pos_labels: 中心词周围 context window 出现过的单词 [batch_size * (window_size * 2)] neg_labelss: 中心词周围没有出现过的单词,从 negative sampling 得到 [batch_size, (window_size * 2 * K)] return: loss ''' input_embedding = self.in_embed(input_labels) # B * embed_size pos_embedding = self.out_embed(pos_labels) # B * (2*C) * embed_size neg_embedding = self.out_embed(neg_labels) # B * (2*C * K) * embed_size print("input_embedding size:", input_embedding.size()) print("pos_embedding size:", pos_embedding.size()) print("neg_embedding size:", neg_embedding.size()) log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze() # B * (2*C) log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # B * (2*C*K)先拓展再压缩 print("log_pos size:", log_pos.size()) print("log_neg size:", log_neg.size()) log_pos = F.logsigmoid(log_pos).sum(1)#1为横向压缩,2为竖向压缩,可以试试玩 log_neg = F.logsigmoid(log_neg).sum(1) loss = log_pos + log_neg print("log_pos size:", log_pos.size()) print("log_neg size:", log_neg.size()) print("loss size:", loss.size()) return -loss def input_embeddings(self): return self.in_embed.weight.data.cpu().numpy()model = EmbeddingModel(VOCAB_SIZE, EMBEDDING_SIZE)if USE_CUDA: model = model.cuda()if __name__ == '__main__': optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) for e in range(NUM_EPOCHS): for i, (input_labels, pos_labels, neg_labels) in enumerate(dataloader): print(input_labels.size()) print(pos_labels.size()) print(neg_labels.size()) input_labels = input_labels.long() pos_labels = pos_labels.long() neg_labels = neg_labels.long() if USE_CUDA: input_labels = input_labels.cuda() pos_labels = pos_labels.cuda() neg_labels = neg_labels.cuda() optimizer.zero_grad() loss = model(input_labels, pos_labels, neg_labels).mean() loss.backward() optimizer.step() if i % 100 == 0: with open(LOG_FILE, "a") as fout: fout.write("epoch: {}, iter: {}, loss: {}\n".format(e, i, loss.item())) print("epoch: {}, iter: {}, loss: {}".format(e, i, loss.item())) embedding_weights = model.input_embeddings() torch.save(model.state_dict(), "embedding-{}.th".format(EMBEDDING_SIZE))使用场景:
    1.item title检索 +ANN+word avg,word average: – 对所有输入词向量求和并取平均 – 例如:输入三个4维词向量:(1,2,3,4),(9,6,11,8),(5,10,7,12) – 那么我们word2vec映射后的词向量就是(5,6,7,8)
    2.模型初始化:处理特征等
    3.近义词(语义理解):可通过计算词向量的加减,与相似度可进行语义理解
    实践例:
    def evaluate(filename, embedding_weights): if filename.endswith(".csv"): data = pd.read_csv(filename, sep=",") else: data = pd.read_csv(filename, sep="\t") human_similarity = [] model_similarity = [] for i in data.iloc[:, 0:2].index: word1, word2 = data.iloc[i, 0], data.iloc[i, 1] if word1 not in word_to_idx or word2 not in word_to_idx: continue else: word1_idx, word2_idx = word_to_idx[word1], word_to_idx[word2] word1_embed, word2_embed = embedding_weights[[word1_idx]], embedding_weights[[word2_idx]] model_similarity.append(float(sklearn.metrics.pairwise.cosine_similarity(word1_embed, word2_embed))) human_similarity.append(float(data.iloc[i, 2])) return scipy.stats.spearmanr(human_similarity, model_similarity)# , model_similaritydef find_nearest(word): index = word_to_idx[word] embedding = embedding_weights[index] cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])#1-cosine return [idx_to_word[i] for i in cos_dis.argsort()[:10]]model.load_state_dict(torch.load("model/embedding-{}.th".format(EMBEDDING_SIZE), map_location='cpu'))embedding_weights = model.input_embeddings()# 找相似的单词for word in ["good", "computer", "china", "mobile", "study"]: print(word, find_nearest(word))# 单词之间的关系,寻找nearest neighborsman_idx = word_to_idx["man"]king_idx = word_to_idx["king"]woman_idx = word_to_idx["woman"]embedding = embedding_weights[woman_idx] - embedding_weights[man_idx] + embedding_weights[king_idx]cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])for i in cos_dis.argsort()[:20]: print(idx_to_word[i])
    1 留言 2019-06-23 14:57:53 奖励13点积分
  • 中山大学智慧健康服务平台应用开发-Broadcast使用

    一、实验题目中山大学智慧健康服务平台应用开发
    二、实现内容2.1 Broadcast 使用2.1.1 实验目的
    掌握 Broadcast 编程基础
    掌握动态注册 Broadcast 和静态注册 Broadcast
    掌握Notification 编程基础
    掌握 EventBus 编程基础

    2.1.2 实验内容在之前的基础上,实现静态广播、动态广播两种改变Notification 内容的方法。
    要求在启动应用时,会有通知产生,随机推荐一个食品

    点击通知跳转到所推荐食品的详情界面

    点击收藏图标,会有对应通知产生,并通过Eventbus在收藏列表更新数据

    点击通知返回收藏列表

    实现方式要求:启动页面的通知由静态广播产生,点击收藏图标的通知由动态广播产生。
    2.1.3 验收内容
    静态广播:启动应用是否有随机推荐食品的通知产生。点击通知是否正确跳转到所推荐食品的详情界面
    动态广播:点击收藏后是否有提示食品已加入收藏列表的通知产生。同时注意设置launchMode。点击通知是否跳转到收藏列表
    Eventbus:点击收藏列表图标是否正确添加食品到收藏列表。每点击一次,添加对应的一个食品到收藏列表并产生一条通知

    三、实验结果3.1 实验截图下图为打开app后,产生一个推荐食品的通知

    下图为点击该通知,会跳转至食物详情页面。点击收藏按钮时,产生收藏的通知

    下图为点击收藏通知,跳转至收藏列表页面

    3.2 实验步骤以及关键代码3.2.1 利用静态广播实现今日推荐功能在AndroidManifest.xml注册静态广播接受方其中StaticReceiver为类名
    <receiver android:name=".StaticReceiver"> <intent-filter> <action android:name="com.example.asus.health.MyStaticFilter" /> </intent-filter> </receiver>
    实现StaticReceiver类,重构onReceive函数其中要根据intent的action来确定是否接受该广播的内容,来实现功能,而需要实现的包括一个notification的弹出以及点击它跳转到详情页面。
    notification部分由builder的设置函数来设置名字,内容,等等,由NotificationManager来发出该notification。
    点击后跳转的功能则需要给builder设置一个ContentIntent,这个intent为PeddingIntent,即不会马上跳转,而是需要等待用户的操作。它的构造函数传递了一个普通的intent,而这个intent是携带了所需的数据来生成详情页面。
    public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(STATICACTION)){ Bundle bundle = intent.getExtras(); //TODO:添加Notification部分 Notification.Builder builder = new Notification.Builder(context); //跳回主页面 Intent intent2 = new Intent(context,Details.class); Bundle bundle2 = new Bundle(); String s[] = new String [5]; s[0] = ((MyCollection)bundle.getSerializable("collect")).getName(); s[1] = ((MyCollection)bundle.getSerializable("collect")).getMaterial(); s[2] = ((MyCollection)bundle.getSerializable("collect")).getType(); s[3] = ((MyCollection)bundle.getSerializable("collect")).getContent(); s[4] = ((MyCollection)bundle.getSerializable("collect")).getIs_star()?"yes":"no"; bundle2.putStringArray("msg",s); intent2.putExtras(bundle2); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT); //对Builder进行配置 builder.setContentTitle("今日推荐") //设置通知栏标题:发件人 .setContentText(((MyCollection)bundle.getSerializable("collect")).getName()) //设置通知栏显示内容:短信内容 .setTicker("您有一条新消息") //通知首次出现在通知栏,带上升动画效果的 .setSmallIcon(R.mipmap.empty_star) //设置通知小ICON 空星 .setContentIntent(contentIntent) //传递内容 .setAutoCancel(true); //设置这个标志当用户单击面板就可以让通知将自动取消 //获取状态通知栏管理 NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); //绑定Notification,发送通知请求 Notification notify = builder.build(); manager.notify(0,notify); } }
    在FoodList主页面onCreat时生成广播注意action的字符串要与上面的Reciver的相同,不然无法正确接受广播,随机数则是返回一个0到n-1的整数表示随机生成一个推荐食物,然后将所需数据放入intent,通过sendBroadcast函数发送该广播。
    //打开应用时,发送一个静态广播 private void boardcastforOpen(int n){ final String STATICACTION = "com.example.asus.health.MyStaticFilter"; Random random = new Random(); int num = random.nextInt(n); //返回一个0到n-1的整数 Intent intentBroadcast = new Intent(STATICACTION); //定义Intent Log.i("se",getPackageName()); Bundle bundles = new Bundle(); bundles.putSerializable("collect", data2.get(num)); intentBroadcast.putExtras(bundles); sendBroadcast(intentBroadcast); }
    3.2.2 利用动态广播实现收藏信息提示实现广播接受器DynamicReceiver类与静态Receiver的实现过程差不多,一样是实现builder,然后放置peddingIntent,这里就不再重复放代码。唯一的不同点在于,它所要跳回的是收藏夹页面,即FoodList主页面,这里要对intent设置flag,否则无法在foodlist中get到新的intent。
    //跳回收藏夹 Intent intent2 = new Intent(context,FoodList.class); Bundle bundle2 = new Bundle(); bundle2.putString("tag","collect"); intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent2.putExtras(bundle2);
    对详情页面的收藏事件进行处理在监听器中设置发送广播的intent,当按下收藏后会发出广播,并传递参数。其中还使用了eventbus来传递收藏的数据。
    //处理收藏按钮 final ImageView collect_but = findViewById(R.id.collect); collect_but.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ temp.setIs_collected(true); Toast.makeText(Details.this, "已收藏",Toast.LENGTH_SHORT).show(); EventBus.getDefault().post(new MessageEvent(temp)); //发送广播 Intent intentBroadcast = new Intent(); intentBroadcast.setAction(DYNAMICACTION); sendBroadcast(intentBroadcast); } });
    在FoodList注册动态接收器以及注销动态接收器注意分别要在onCreate函数以及onDestroy函数中实现注册与注销 。
    3.2.3 使用EventBus来实现数据的传输在这一点上,要改进上一周实验的代码,不再需要点击返回按钮利用setResult以及onActivityResult两个函数来返回信息。而是通过eventbus的订阅发布模式。
    在FoodList来注册订阅者,订阅消息。而在Detail来发布信息。其中onMessageEvent函数用于收到发布消息后,来调用之前的接口函数刷新列表。
    发布消息就是上面点击收藏按钮后 EventBus.getDefault().post(new MessageEvent(temp));
    //注册订阅者(注册收藏列表所在Activity为订阅者) EventBus.getDefault().register(this); @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Log.i("hello","this is eventbus."); MyCollection mc = event.getCollection(); refreshList(mc,simpleAdapter); }
    3.2.4 从详情跳转回收藏夹这里由于收藏夹FoodList为经常返回的页面,故这里使用了android:launchMode=”singleInstance”,即不让它重复创建新的活动。
    所以再get我的返回intent时是拿不到新的intent的,这里需要重写onNewIntent函数,而且接收新的intent要在onResume中。
    这里要求要显示收藏夹页面,所以要将食物列表隐藏起来。
    @Override protected void onNewIntent(Intent intent){ super.onNewIntent(intent); setIntent(intent); } @Override protected void onResume(){ super.onResume(); //处理跳转 Bundle bundle=this.getIntent().getExtras(); if(bundle != null) { String str = bundle.getString("tag"); Log.i("resume2",str); if (str.equals("collect")) { findViewById(R.id.recyclerView).setVisibility(View.GONE); findViewById(R.id.listView).setVisibility(View.VISIBLE);//设置Favourite可见 tag_for_foodlist = false; f_but.setImageResource(R.mipmap.mainpage); } } }
    3.3 实验遇到的困难以及解决思路3.3.1 在安卓8.0版本中无法使用静态接收器,发送广播后,无法成功接收方法一:解决这个问题,需要给receiver设置component,给予它的包名以及类名。
    intent.setComponent(new ComponentName(getPackageName(),getPackageName()+".xxxxReceiver"));
    方法二:下载新的虚拟机,使用安卓7.0版本,则可以顺利接收静态广播。
    3.3.2 使用EventBus时候,FoodList主页面无法得到post的信息我按部就班地在Detail页面收藏按钮post,在FoodList订阅消息却毫无反应。首先,我认为是我的接收函数写错了,没有订阅到信息。通过Log.i发现确实没有进入到onMessageEvent函数中,于是对这个问题进行了查阅。网上有推荐使用stickyPost的,怀疑原因出在信息接收发生在创建接收者之前,但显然与函数执行顺序不符,它是先来到了主页面,所以必然创建了receiver。
    经过大半个小时的查找发现是,post传的参数错误,并没有生成MessageEvent,而是错误地直接传递了数据包。
    //错误EventBus.getDefault().post(temp);//正确EventBus.getDefault().post(new MessageEvent(temp));
    3.3.3 从收藏通知返回主页面时候,出现无法拿到intent的情况由于我是在动态接收方的builder绑定了Peddingintent,当点击通知,应该要返回这个intent到主页面,然而主页面所获取的intent是空值。这一点让我怀疑了很久,问了同学才得知,这是声明了singleInstance的问题。
    比如说在一个应用中A activity 跳转至 B activity 在跳转至 C activity 然后C做了一定的操作之后再返回A 界面。这样在A activity的启动模式设置为singleTask后。C界面跳转至A界面时,就会去判断栈内是否有改Activity实例,如果有就直接执行A界面的onNewIntent()方法,我们就可以把逻辑处理放在改生命周期方法中,如果没有就会走Activity的oncrate方法去创建实例。
    所以这里需要重写onNewIntent来获取新的intent,而不是直接传递旧intent导致错误。
    @Override protected void onNewIntent(Intent intent){ super.onNewIntent(intent); setIntent(intent); }
    四、实验思考及感想这次实验需要在安卓8.0与安卓7.0之间权衡,有些属性方法已经在8.0版本出现了变化,所以当使用错误,出现奇怪的现象时,第一步先检查自己的代码逻辑有否问题,第二步就是要查阅是否存在版本的兼容性问题产生了这些错误。这次作业就是如此,关于广播的实现,个人还是喜欢动态广播,不需要再静态注册在manifest中,代码也更加简便。
    对于不同活动之间的传输,使用EventBus比之前的intent更加方便,减轻了耦合性,不用经常记住,哪个intent返回哪里,所以这次我也修改了不少前面实验使用intent的代码。除此之外,充分理解信息传输还需要理解一下活动的存活过程,什么时候调用onCreat,什么时候使用onResume。
    2 留言 2019-07-17 23:38:40 奖励17点积分
  • 基于WinInet实现的HTTP文件下载 精华

    背景之前写过的网络数据传输的小程序都是基于Socket去写的,所以,如果要用Socket传输数据到网站,还需要根据域名获取服务器的IP地址,然后再建立连接,传输数据。虽然,Socket也可以实现网络传输,但是,总感觉不是很方便。所以,后来随着知识面的拓展,了解到Windows还专门提供了WinInet网络库,封装了比较简便的接口,去实现HTTP和FTP等传输协议的数据传输。
    本文就是基于WinInet网络库,实现通过HTTP传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。
    主要函数介绍介绍HTTP下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. InternetReadFile介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考本站上的的 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTP的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求
    使用 HttpSendRequest 发送访问请求
    根据返回的Response Header的数据中,获取将要接收数据的长度
    使用 InternetReadFile 接收数据
    关闭句柄,释放资源

    其中,上面的 8 个步骤中,要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTP文件下载编程实现// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL Http_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; ::RtlZeroMemory(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Http_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据下载 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Http_ShowError("InternetOpen"); break; } // 建立连接 hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Http_ShowError("InternetConnect"); break; } // 打开并发送HTTP请求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Http_ShowError("HttpOpenRequest"); break; } // 发送请求 bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { Http_ShowError("HttpSendRequest"); break; } // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Http_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTP_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Http_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Http_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回数据 *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 关闭 释放 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    程序测试在main函数中,调用上述封装好的函数,下载文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpDownloadUrl[] = "http://www.demongan.com/source/ccc/dasanxia/520.zip"; BYTE *pHttpDownloadData = NULL; DWORD dwHttpDownloadDataSize = 0; // HTTP下载 if (FALSE == Http_Download(szHttpDownloadUrl, &pHttpDownloadData, &dwHttpDownloadDataSize)) { return 1; } // 将下载数据保存成文件 Http_SaveToFile("http_downloadsavefile.zip", pHttpDownloadData, dwHttpDownloadDataSize); // 释放内存 delete []pHttpDownloadData; pHttpDownloadData = NULL; system("pause"); return 0;}
    测试结果:
    根据返回的Response Header知道,成功下载22761460字节大小的数据。

    查看目录,有22228KB大小的“http_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

    总结基于WinInet库的HTTP下载文件原理并不复杂,但是,因为涉及较多的API,每个API的执行都需要依靠上一个API成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。
    参考参考自《Windows黑客编程技术详解》一书
    3 留言 2018-12-20 17:46:07 奖励13点积分
显示 0 到 15 ,共 15 条
eject